1 Linux SPI驱动框架

img linux SPI驱动框架层次如上图:

除开硬件和用户态应用程序,由上到下分成3层:

设备驱动层: spi框架使用者  
核心层:spi框架搭建者  
控制器驱动层: spi框架适配者

1.1 spi核心层

SPI核心层代码位于linux_5.10\drivers\spi目录:

# SPDX-License-Identifier: GPL-2.0  
#  
# Makefile for kernel SPI drivers.  
#  
ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG  
# small core, mostly translating board-specific  
# config declarations into driver model code  
obj-$(CONFIG_SPI_MASTER)		+= spi.o  
obj-$(CONFIG_SPI_MEM)			+= spi-mem.o  
obj-$(CONFIG_SPI_MUX)			+= spi-mux.o  
obj-$(CONFIG_SPI_SPIDEV)		+= spidev.o  
obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o  
# SPI master controller drivers (bus)  
obj-$(CONFIG_SPI_ALTERA)		+= spi-altera.o  
obj-$(CONFIG_SPI_AR934X)		+= spi-ar934x.o  
obj-$(CONFIG_SPI_ARMADA_3700)		+= spi-armada-3700.o  
obj-$(CONFIG_SPI_ATMEL)			+= spi-atmel.o  
obj-$(CONFIG_SPI_ATMEL_QUADSPI)		+= atmel-quadspi.o  
obj-$(CONFIG_SPI_AT91_USART)		+= spi-at91-usart.o  
obj-$(CONFIG_SPI_ATH79)			+= spi-ath79.o  
obj-$(CONFIG_SPI_AU1550)		+= spi-au1550.o  
obj-$(CONFIG_SPI_AXI_SPI_ENGINE)	+= spi-axi-spi-engine.o  
obj-$(CONFIG_SPI_BCM2835)		+= spi-bcm2835.o
drivers/spi/spi.c spi-mem.c spi-mux.c  
include/linux/spi/spi.h  
spi.c:  
    一方面对SPI子系统进行初始化工作,注册spi bus,注册spi_master class,  
    同时提供spi设备驱动对spi总线进行操作的API。  
    另一方面SPI子系统对spi控制器层,提供注册控制器的api和回调操作函数。  
spi.h包含了spi核心层的一些重要数据结构,struct spi_master;   
	struct spi_transfer; struct spi_message,以及一些实现比较简单的函数等。  
  
spi-gpio.c:SPI GPIO框架:SPI子系统提供了一个名为spi-gpio的框架,  
        可使用GPIO引脚模拟SPI总线,gpio模拟spi代码在drivers/spi/spi-gpio.c中。  
        这个框架允许将GPIO引脚配置为SPI总线的时钟、片选、输入和输出信号,  
        并提供了对应的接口函数供驱动程序使用。  
spi-bitbang:spi-bitbang是Linux内核中提供的一个通用框架,用于在没有硬件SPI控制器  
        或需要灵活控制SPI时序和配置的系统中模拟SPI总线的通信。代码在spi-bitbang.c中

核心层的作用:

对上层的使用者,也就是SPI设备驱动:提供标准的spi收发API,以及设备注册函数。
对底下的适配者,也就是控制器驱动层:提供注册控制器接口,并提供一些需要控制器驱动实现的回调函数。

1.1.1 核心层初始化

1.1.1.1 spi_init

static int __init spi_init(void) {  
	int	status;  
	buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);  
	if (!buf) {  
		status = -ENOMEM;  
		goto err0;  
	}  
	status = bus_register(&spi_bus_type);  
	if (status < 0)  
		goto err1;  
	status = class_register(&spi_master_class);  
	if (status < 0)  
		goto err2;  
	if (IS_ENABLED(CONFIG_SPI_SLAVE)) {  
		status = class_register(&spi_slave_class);  
		if (status < 0)  
			goto err3;  
	}  
  
	if (IS_ENABLED(CONFIG_OF_DYNAMIC))  
		WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));  
	if (IS_ENABLED(CONFIG_ACPI))  
		WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));  
  
	return 0;  
err3:  
	class_unregister(&spi_master_class);  
err2:  
	bus_unregister(&spi_bus_type);  
err1:  
	kfree(buf);  
	buf = NULL;  
err0:  
	return status;  
}

spi子系统初始化函数,仅仅是注册了spi bus,以及spi_master class。

成功注册后,在/sys/bus 下即可找到spi 文件目录,在/sys/class下可以看到spi_master目录:

img img

1.1.1.2 spi从设备和驱动的匹配

当spi总线和类注册后,当有驱动和设备匹配上就会调用spi_match_device,也就是spi_bus_type.match函数。

static int spi_match_device(struct device *dev, struct device_driver *drv) {  
	const struct spi_device	*spi = to_spi_device(dev);  
	const struct spi_driver	*sdrv = to_spi_driver(drv);  
  
	/* Check override first, and if set, only use the named driver */  
	if (spi->driver_override)  
		return strcmp(spi->driver_override, drv->name) == 0;  
  
	/* Attempt an OF style match */  
	if (of_driver_match_device(dev, drv))  
		return 1;  
  
	/* Then try ACPI */  
	if (acpi_driver_match_device(dev, drv))  
		return 1;  
  
	if (sdrv->id_table)  
		return !!spi_match_id(sdrv->id_table, spi);  
	return strcmp(spi->modalias, drv->name) == 0;  
}
  1. 通过of_match_table的compatible和设备树匹配;
  2. 通过acpi_match_table的compatible和device的of_node的compatible匹配;
  3. 通过驱动和设备的id_table去匹配。
  4. 最后通过驱动和设备的名字去匹配。

1.1.2 核心层API

1.1.2.1 spi_register_master-(供适配层调用)

int spi_register_master(struct spi_master *master) {  
	...  
	status = of_spi_register_master(master);  
	if (status)  
		return status;  
	...  
	dev_set_name(&master->dev, "spi%u", master->bus_num);  
	status = device_add(&master->dev);  
	if (status < 0)  
		goto done;  
	dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),  
			dynamic ? " (dynamic)" : "");  
  
	if (master->transfer)  
		dev_info(dev, "master is unqueued, this is deprecated\n");  
	else {  
		status = spi_master_initialize_queue(master);  
		if (status) {  
			device_del(&master->dev);  
			goto done;  
		}  
	}  
	...  
	mutex_lock(&board_lock);  
	list_add_tail(&master->list, &spi_master_list);  
	list_for_each_entry(bi, &board_list, list)  
		spi_match_master_to_boardinfo(master, &bi->board_info);  
	mutex_unlock(&board_lock);  
	...  
	of_register_spi_devices(master);  
	...  
done:  
	return status;  
}
  1. of_spi_register_master,根据设备树节点中的"cs-gpios",向struct spi_master添加gpio cs引脚。

  2. device_add将device注册到设备模型中。

  3. 如果控制器驱动没有自己实现transfer函数,则初始化发送队列spi_master_initialize_queue。(核心层填充默认transfer函数)

  4. spi_match_master_to_boardinfo老的方式,遍历所有spi_board_info数据结构,并注册spi_device

  5. of_register_spi_devices新的设备树方式,遍历spi控制器节点下所有子节点,并注册成对应的spi_device设备

1.1.2.1.1 spi_controller_initialize_queue/spi_master_initialize_queue

img 可以看到是对控制器spi_master(或者叫做spi_controller)成员函数赋值。transfer_one_message或者transfer赋值成默认的函数。然后调用spi_init_queue和spi_start_queue函数初始化队列并启动工作线程。spi_init_queue函数最主要的作用就是建立一个内核工作线程。

static int spi_init_queue(struct spi_master *master)  
{  
	......  
	kthread_init_worker(&master->kworker);  
	master->kworker_task = kthread_run(kthread_worker_fn,  
            &master->kworker, "%s", dev_name(&master->dev));  
	......  
	kthread_init_work(&master->pump_messages, spi_pump_messages);  
	......  
	return 0;  
}  
static int spi_start_queue(struct spi_master *master)  
{  
	......  
	master->running = true;  
	master->cur_msg = NULL;  
	......  
	kthread_queue_work(&master->kworker, &master->pump_messages);  
	......  
	return 0;  
}  
/* spi_init_queue函数先初始化kthread_worker,为kthread_worker创建一个内核线程来处理work,  
		随后初始化kthread_work,设置work执行函数,work执行函数为spi_pump_messages  
spi_start_queue就相对简单了,只是唤醒该工作线程而已;自此,队列化的相关工作已经完成,  
		系统等待message请求被发起,然后在工作线程中处理message的传送工作。  
*/

1.1.2.2 spi_message_init-(供从设备调用)

对spi_massage进行初始化.

static inline void spi_message_init_no_memset(struct spi_message *m)  
{  
	INIT_LIST_HEAD(&m->transfers);  
	INIT_LIST_HEAD(&m->resources);  
}  
static inline void spi_message_init(struct spi_message *m)  
{  
	memset(m, 0, sizeof *m);  
	spi_message_init_no_memset(m);  
}

1.1.2.3 spi_message_add_tail-(供从设备调用)

static inline void  
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) {  
	list_add_tail(&t->transfer_list, &m->transfers);  
}

可以看到就是把spi_transfer这个buffer添加到spi_message传输链表中。

1.1.2.4 spi_async-(供从设备调用)

发起数据传输。可以看到就是调用控制器内部的master->transfer。既然是async那就是异步执行的,不会等待传输是否完成,就直接返回。

spi_async->  
		__spi_async->  
				master->transfer

那假如master->transfer控制器这边没有去实现。内核会自己填充默认的transfer函数spi_queued_transfer.

1.1.2.4.1 SPI_QUEUED_TRANSFER
spi_queued_transfer(struct spi_device *spi, struct spi_message *msg) {  
		__spi_queued_transfer(spi, msg, true);  
}  
static int __spi_queued_transfer(struct spi_device *spi,  
				 struct spi_message *msg,  
				 bool need_pump) {  
	struct spi_master *master = spi->master;  
	unsigned long flags;  
  
	spin_lock_irqsave(&master->queue_lock, flags);  
	...  
	list_add_tail(&msg->queue, &master->queue);  
	if (!master->busy && need_pump)  
		kthread_queue_work(&master->kworker, &master->pump_messages);  
	spin_unlock_irqrestore(&master->queue_lock, flags);  
	return 0;  
}

可以看到默认的spi_queued_transfer就是去把message添加到master的发送链表中。接下来就交给工作服务线程__spi_pump_messages去处理message。

1.1.2.5 spi_sync-(供从设备调用)

img 同理,spi_sync就是用来同步传输 spi_message, 完成传输后会调用spi_comlete唤醒等待的线程。

那假如master->transfer控制器这边没有去实现。内核也会自己填充默认的transfer函数spi_queued_transfer.可以看到need_pump=false,因此还是一样就是去把message添加到master的发送链表中。

然后调用__spi_pump_messages,也就是工作线程服务,交给工作服务线程去处理message。

最后调用wait_for_completion去等待传输完成。

1.1.2.6 spi_bitbang_start-(供适配层调用)

int spi_bitbang_start(struct spi_bitbang *bitbang)  
{  
    struct spi_master *master = bitbang->master;  
    int ret;  
  
    if (!master | !bitbang->chipselect)  
        return -EINVAL;  
  
    mutex_init(&bitbang->lock);  
  
    if (!master->mode_bits)  
        master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;  
  
    if (master->transfer | master->transfer_one_message)  
        return -EINVAL;  
  
    master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;  
    master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;  
    master->transfer_one = spi_bitbang_transfer_one;  
    master->set_cs = spi_bitbang_set_cs;  
  
    if (!bitbang->txrx_bufs) {  
        bitbang->use_dma = 0;  
        bitbang->txrx_bufs = spi_bitbang_bufs;  
        if (!master->setup) {  
            if (!bitbang->setup_transfer)  
                bitbang->setup_transfer =  
                     spi_bitbang_setup_transfer;  
            master->setup = spi_bitbang_setup;  
            master->cleanup = spi_bitbang_cleanup;  
        }  
    }  
  
    /* driver may get busy before register() returns, especially  
     * if someone registered boardinfo for devices  
     */  
    ret = spi_register_master(spi_master_get(master));  
    if (ret)  
        spi_master_put(master);  
  
    return ret;  
}
  1. 如果主设备结构体中的传输模式位字段 mode_bits 未设置,则设置为默认的模式位,包括 SPI_CPOL、SPI_CPHA 和位移传输控制结构体中的标志位。

  2. 检查主设备结构体中的传输函数是否已经定义,如果已定义则返回错误码 -EINVAL。

  3. 设置主设备结构体中的准备硬件传输函数、释放硬件传输函数、单次传输函数和片选信号控制函数,分别对应位移传输控制结构体中的对应函数。

  4. 如果位移传输控制结构体中的数据缓冲区传输函数未定义,则设置使用 DMA 标志为 0,并将数据缓冲区传输函数设置为默认的位移传输函数 spi_bitbang_bufs。如果主设备结构体中的设置函数未定义,则设置使用默认的设置函数 spi_bitbang_setup 和清理函数 spi_bitbang_cleanup。

  5. 注册 SPI 主设备,将其添加到系统中。

1.1.2.5.1 spi_register_master

1.2 SPI控制器驱动层

控制器驱动就是用来实现spi_master中的成员函数。如transfer, transfer_one_message。基本流程是:申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master。

1.2.1 SPI控制器API

1.2.1.1 spi_alloc_master

申请spi控制器内存。

struct spi_master *spi_alloc_master(struct device *dev, unsigned size);

dev:设备,一般是 platform_device 中的 dev 成员变量。

size:私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。

返回值:申请到的 spi_master。

1.2.1.2 spi_master_put

spi_master 的释放通过 spi_master_put函数来完成,当我们删除一个 SPI 主机驱动的时候就 需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:

void spi_master_put(struct spi_master *master);

释放spi控制器内存。

1.2.1.3 spi_register_master/spi_register_controller

当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_register_master注册spi控制器。

int spi_register_master(struct spi_master *master);

1.2.1.4 spi_unregister_master/spi_unregister_controller

spi控制器卸载。

void spi_unregister_master(struct spi_master *master);

1.2.2 SPI控制器示例

以飞思卡尔nxp官方spi驱动为例,文件位于linux\drivers\spi\spi-imx.c

1.2.2.1 spi控制器设备树描述

打开设备树文件imx6ul.dtsi:

img

ecspi3: spi@2010000 {  
        #address-cells = <1>;  
        #size-cells = <0>;  
        compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";  
        reg = <0x02010000 0x4000>;  
        interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;  
        clocks = <&clks IMX6UL_CLK_ECSPI3>,  
                 <&clks IMX6UL_CLK_ECSPI3>;  
        clock-names = "ipg", "per";  
        dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;  
        dma-names = "rx", "tx";  
        status = "disabled";  
};

1.2.2.2 imx6ul spi控制器驱动

1.2.2.2.1 probe-(初始化spi_master)

img

  1. 解析设备树,初始化控制器

    img 配置master和spi_imx。包括传输函数,片选函数,片选引脚。master作为平台设备的dev的driver_data, spi_imx作为master的dev的driver_data。

    获取irq, res等信息,进行ioremap和注册irq。

    设置时钟,开启时钟。

    初始话dma寄存器。进行控制器初始化和复位。

    img img

  2. 申请并初始化 spi_master, 调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册 spi_master。

1.2.2.2.2 spi_imx_setupxfer-(设置位宽和配置控制器)

img 设置 spi_imx 的 tx 和 rx 传输函数。根据要发送的数据数据位宽的不 同,分别有 8 位、16 位和 32 位的发送函数:

spi_imx_buf_tx_u8  
spi_imx_buf_tx_u16  
spi_imx_buf_tx_u32  
spi_imx_buf_rx_u8  
spi_imx_buf_rx_u16  
spi_imx_buf_rx_u32
#define MXC_SPI_BUF_RX(type)						\  
static void spi_imx_buf_rx_##type(struct spi_imx_data *spi_imx)		\  
{	    //将要接收的数据值读到 ECSPI 的 MXC_CSPIRXDATA 寄存器里面去	\  
	unsigned int val = readl(spi_imx->base + MXC_CSPIRXDATA);	\  
									\  
	if (spi_imx->rx_buf) {						\  
		*(type *)spi_imx->rx_buf = val;				\  
		spi_imx->rx_buf += sizeof(type);			\  
	}								\  
}  
#define MXC_SPI_BUF_TX(type)						\  
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx)		\  
{									\  
	type val = 0;							\  
									\  
	if (spi_imx->tx_buf) {						\  
		val = *(type *)spi_imx->tx_buf;				\  
		spi_imx->tx_buf += sizeof(type);			\  
	}								\  
									\  
	spi_imx->count -= sizeof(type);					\  
//将要发送的数据值写入到 ECSPI 的 TXDATA 寄存器里面去		\  
	writel(val, spi_imx->base + MXC_CSPITXDATA);	\  
}  
  
MXC_SPI_BUF_RX(u8)  
MXC_SPI_BUF_TX(u8)  
MXC_SPI_BUF_RX(u16)  
MXC_SPI_BUF_TX(u16)  
MXC_SPI_BUF_RX(u32)  
MXC_SPI_BUF_TX(u32)

img img 调用关系如下:mx51_ecspi_config就是最底层SPI controller的寄存器配置,这里不做分析。

spi_imx->bitbang.setup_transfer = spi_imx_setupxfer  
	spi_imx->devtype_data->config = mx51_ecspi_config
1.2.2.2.3 spi_imx_transfer-(数据收发)

数据收发函数为 spi_imx_transfer:

spi_imx_transfer  
    -> spi_imx_pio_transfer  
        -> spi_imx_push  
        	-> spi_imx->tx

spi_imx 是个 spi_imx_data 类型的机构指针变量,其中 tx 和 rx 这两个成员变量分别为 SPI 数据发送和接收函数。

spi_bitbang_start中  
    ->master->transfer_one = spi_bitbang_transfer_one;  
当spi_bitbang_transfer_on时  
    bitbang->txrx_bufs(spi,t) = spi_imx_transfer  
    也就是spi_imx->tx就可以spi_imx_buf_tx_u8
1.2.2.2.4 spi_imx_isr

img 中断服务程序,只要rx_available,启用spi_imx->rx。从MXC_CSPIRXDATA 寄存器读出数据。

static int __maybe_unused mx51_ecspi_rx_available(struct spi_imx_data *spi_imx)  
{  
	return readl(spi_imx->base + MX51_ECSPI_STAT) & MX51_ECSPI_STAT_RR;  
}  
#define MXC_SPI_BUF_RX(type)						\  
static void spi_imx_buf_rx_##type(struct spi_imx_data *spi_imx)		\  
{									\  
	unsigned int val = readl(spi_imx->base + MXC_CSPIRXDATA);	\  
									\  
	if (spi_imx->rx_buf) {						\  
		*(type *)spi_imx->rx_buf = val;				\  
		spi_imx->rx_buf += sizeof(type);			\  
	}								\  
}
1.2.2.2.5 SPI_IMX_CHIPSELECT-(片选)

img 设置片选gpio电平。

1.3 SPI从设备驱动层

1.3.1 SPI从设备API

1.3.1.1 spi_message_init

void spi_message_init(struct spi_message *m);

在使用spi_message之前需要对其进行初始化。

1.3.1.2 spi_message_add_tail

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中。

1.3.1.3 spi_async

参考核心层api有介绍。

1.3.1.4 spi_sync

参考核心层api有介绍。

1.3.1.5 spi_register_driver

1.3.1.6 spi_unregister_driver

1.3.1.7 spi_read/spi_write

static inline void spi_message_init_with_transfers(struct spi_message *m,  
             struct spi_transfer *xfers, unsigned int num_xfers) {  
	unsigned int i;  
	spi_message_init(m);  
	for (i = 0; i < num_xfers; ++i)  
		spi_message_add_tail(&xfers[i], m);  
}  
static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,  
             unsigned int num_xfers) {  
	struct spi_message msg;  
	spi_message_init_with_transfers(&msg, xfers, num_xfers);  
	return spi_sync(spi, &msg);  
}  
static inline int spi_read(struct spi_device *spi, void *buf, size_t len) {  
	struct spi_transfer	t = {  
			.rx_buf		= buf,  
			.len		= len,  
		};  
	return spi_sync_transfer(spi, &t, 1);  
}  
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)  
{  
	struct spi_transfer	t = {  
			.tx_buf		= buf,  
			.len		= len,  
		};  
  
	return spi_sync_transfer(spi, &t, 1);  
}

spi_read, spi_write本质还是调用spi_sync传输函数,做了一层封装把spi_transfrer和spi_message包装好了。

1.3.2 SPI从设备示例-ICM-20608-G

参考ICM-20608-G开发手册。

1.3.2.1 dts描述

打开自己board对应的dts,我这里是imx6ull-alientek-emmc.dts。我们修改ecspi3 spi控制器。我的spi3接了一个ICM-20608。资源定义如下:

img 根据原理图接线先配置iomux:

pinctrl_ecspi3: icm20608 {  
	fsl,pins = <  
		MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */  
		MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */  
		MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */  
		MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */  
	>;  
};

UART2_TX_DATA 这个 IO 是 ICM20608 的片选信号,这里我们并没有将其复用为 ECSPI3 的SS0信号,而是将其复用为了普通的 GPIO。因为我们需要自己控制片选信号,所以将其复 用为普通的 GPIO。

imx6ull-alientek-emmc.dts重新修改ecspi3节点,追加从设备描述:

&ecspi3 {  
	fsl,spi-num-chipselects = <1>;  
	cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;  
	pinctrl-names = "default";  
	pinctrl-0 = <&pinctrl_ecspi3>;  
	status = "okay";  
  
	spidev: icm20608@0 {  
		compatible = "alientek,icm20608";  
		spi-max-frequency = <8000000>;  
		reg = <0>;  
	};  
};
  1. 设置当前片选数量为 1,因为就只接了一个 ICM20608。
  2. 一定要使用 “cs-gpios”属性来描述片选引脚,SPI 主机驱动就会控制片选引脚。
  3. imx6ull.dtsi 文件中默认将 ecspi3 节点状态(status)设置为“disable”,这里我们要将 其改为“okay”。
  4. icm20608 设备子节点,因为 icm20608 连接在ECSPI3的第 0 个通道上,因此 @后面为 0。
    1. 设置节点属性兼容值为“alientek,icm20608”。
    2. 设置 SPI 最大时钟频 率为 8MHz,这是 ICM20608 的 SPI 接口所能支持的最大的时钟频率。
    3. icm20608 连接 在通道 0 上,因此 reg 为 0。
1.3.2.1.1 SPI从设备DTS描述规则

打开linux\Documentation\devicetree\bindings\spi\spi-bus.txt

SPI slave nodes must be children of the SPI master node and can  
contain the following properties.  
- reg             - (required) chip select address of device.  
- compatible      - (required) name of SPI device following generic names  
    		recommended practice  
- spi-max-frequency - (required) Maximum SPI clocking speed of device in Hz  
- spi-cpol        - (optional) Empty property indicating device requires  
    		inverse clock polarity (CPOL) mode  
- spi-cpha        - (optional) Empty property indicating device requires  
    		shifted clock phase (CPHA) mode  
- spi-cs-high     - (optional) Empty property indicating device requires  
    		chip select active high  
- spi-3wire       - (optional) Empty property indicating device requires  
    		    3-wire mode.  
- spi-lsb-first   - (optional) Empty property indicating device requires  
		LSB first mode.  
- spi-tx-bus-width - (optional) The bus width(number of data wires) that  
                      used for MOSI. Defaults to 1 if not present.  
- spi-rx-bus-width - (optional) The bus width(number of data wires) that  
                      used for MISO. Defaults to 1 if not present.

1.3.2.2 ICM20608驱动

1.3.2.2.1 icm20608reg.h
#ifndef ICM20608_H  
#define ICM20608_H  
/***************************************************************  
文件名		: icm20608reg.h  
***************************************************************/  
#define ICM20608G_ID			0XAF	/* ID值 */  
#define ICM20608D_ID			0XAE	/* ID值 */  
/* ICM20608寄存器   
 *复位后所有寄存器地址都为0,除了  
 *Register 107(0X6B) Power Management 1 	= 0x40  
 *Register 117(0X75) WHO_AM_I 				= 0xAF或0xAE  
 */  
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */  
#define	ICM20_SELF_TEST_X_GYRO		0x00  
#define	ICM20_SELF_TEST_Y_GYRO		0x01  
#define	ICM20_SELF_TEST_Z_GYRO		0x02  
#define	ICM20_SELF_TEST_X_ACCEL		0x0D  
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E  
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F  
/* 陀螺仪静态偏移 */  
#define	ICM20_XG_OFFS_USRH			0x13  
#define	ICM20_XG_OFFS_USRL			0x14  
#define	ICM20_YG_OFFS_USRH			0x15  
#define	ICM20_YG_OFFS_USRL			0x16  
#define	ICM20_ZG_OFFS_USRH			0x17  
#define	ICM20_ZG_OFFS_USRL			0x18  
#define	ICM20_SMPLRT_DIV			0x19  
#define	ICM20_CONFIG				0x1A  
#define	ICM20_GYRO_CONFIG			0x1B  
#define	ICM20_ACCEL_CONFIG			0x1C  
#define	ICM20_ACCEL_CONFIG2			0x1D  
#define	ICM20_LP_MODE_CFG			0x1E  
#define	ICM20_ACCEL_WOM_THR			0x1F  
#define	ICM20_FIFO_EN				0x23  
#define	ICM20_FSYNC_INT				0x36  
#define	ICM20_INT_PIN_CFG			0x37  
#define	ICM20_INT_ENABLE			0x38  
#define	ICM20_INT_STATUS			0x3A  
/* 加速度输出 */  
#define	ICM20_ACCEL_XOUT_H			0x3B  
#define	ICM20_ACCEL_XOUT_L			0x3C  
#define	ICM20_ACCEL_YOUT_H			0x3D  
#define	ICM20_ACCEL_YOUT_L			0x3E  
#define	ICM20_ACCEL_ZOUT_H			0x3F  
#define	ICM20_ACCEL_ZOUT_L			0x40  
/* 温度输出 */  
#define	ICM20_TEMP_OUT_H			0x41  
#define	ICM20_TEMP_OUT_L			0x42  
/* 陀螺仪输出 */  
#define	ICM20_GYRO_XOUT_H			0x43  
#define	ICM20_GYRO_XOUT_L			0x44  
#define	ICM20_GYRO_YOUT_H			0x45  
#define	ICM20_GYRO_YOUT_L			0x46  
#define	ICM20_GYRO_ZOUT_H			0x47  
#define	ICM20_GYRO_ZOUT_L			0x48  
#define	ICM20_SIGNAL_PATH_RESET		0x68  
#define	ICM20_ACCEL_INTEL_CTRL 		0x69  
#define	ICM20_USER_CTRL				0x6A  
#define	ICM20_PWR_MGMT_1			0x6B  
#define	ICM20_PWR_MGMT_2			0x6C  
#define	ICM20_FIFO_COUNTH			0x72  
#define	ICM20_FIFO_COUNTL			0x73  
#define	ICM20_FIFO_R_W				0x74  
#define	ICM20_WHO_AM_I 				0x75  
/* 加速度静态偏移 */  
#define	ICM20_XA_OFFSET_H			0x77  
#define	ICM20_XA_OFFSET_L			0x78  
#define	ICM20_YA_OFFSET_H			0x7A  
#define	ICM20_YA_OFFSET_L			0x7B  
#define	ICM20_ZA_OFFSET_H			0x7D  
#define	ICM20_ZA_OFFSET_L 			0x7E  
#endif
1.3.2.2.2 icm20608.c

点击查看代码

#include <linux/types.h>  
#include <linux/kernel.h>  
#include <linux/delay.h>  
#include <linux/ide.h>  
#include <linux/init.h>  
#include <linux/module.h>  
#include <linux/errno.h>  
#include <linux/gpio.h>  
#include <linux/cdev.h>  
#include <linux/device.h>  
#include <linux/of_gpio.h>  
#include <linux/semaphore.h>  
#include <linux/timer.h>  
#include <linux/i2c.h>  
#include <linux/spi/spi.h>  
#include <linux/of.h>  
#include <linux/of_address.h>  
#include <linux/of_gpio.h>  
#include <linux/platform_device.h>  
#include <asm/mach/map.h>  
#include <asm/uaccess.h>  
#include <asm/io.h>  
#include "icm20608reg.h"  
#define ICM20608_CNT	1  
#define ICM20608_NAME	"icm20608"  
struct icm20608_dev {  
	dev_t devid;				/* 设备号 	 */  
	struct cdev cdev;			/* cdev 	*/  
	struct class *class;		/* 类 		*/  
	struct device *device;		/* 设备 	 */  
	struct device_node	*nd; 	/* 设备节点 */  
	int major;					/* 主设备号 */  
	void *private_data;			/* 私有数据 		*/  
	signed int gyro_x_adc;		/* 陀螺仪X轴原始值 	 */  
	signed int gyro_y_adc;		/* 陀螺仪Y轴原始值		*/  
	signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 		*/  
	signed int accel_x_adc;		/* 加速度计X轴原始值 	*/  
	signed int accel_y_adc;		/* 加速度计Y轴原始值	*/  
	signed int accel_z_adc;		/* 加速度计Z轴原始值 	*/  
	signed int temp_adc;		/* 温度原始值 			*/  
};  
static struct icm20608_dev icm20608dev;  
  
/*  
 * @description	: 从icm20608读取多个寄存器数据  
 * @param - dev:  icm20608设备  
 * @param - reg:  要读取的寄存器首地址  
 * @param - val:  读取到的数据  
 * @param - len:  要读取的数据长度  
 * @return 		: 操作结果  
 */  
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)  
{  
	int ret = -1;  
	unsigned char txdata[1];  
	unsigned char * rxdata;  
	struct spi_message m;  
	struct spi_transfer *t;  
	struct spi_device *spi = (struct spi_device *)dev->private_data;  
      
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */  
	if(!t) {  
		return -ENOMEM;  
	}  
  
	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */  
	if(!rxdata) {  
		goto out1;  
	}  
    #if 1  
	/* 一共发送len+1个字节的数据,第一个字节为  
	寄存器首地址,一共要读取len个字节长度的数据,*/  
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			  
	t->tx_buf = txdata;			/* 要发送的数据 */  
    t->rx_buf = rxdata;			/* 要读取的数据 */  
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */  
	spi_message_init(&m);		/* 初始化spi_message */  
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */  
	ret = spi_sync(spi, &m);	/* 同步发送 */  
	#else  
    //第一步,发送寄存器地址  
    txdata[0] = reg | 0x80;  
    t->tx_buf = txdata;  
    t->len = 1;  
    spi_message_init(&m);  
	spi_message_add_tail(t, &m);  
	ret = spi_sync(spi, &m);  
      
    //第二步,读取数据  
    txdata[0] = 0xff;  
    t->rx_buf = buf;  
    t->len = len;  
    spi_message_init(&m);  
	spi_message_add_tail(t, &m);  
	ret = spi_sync(spi, &m);  
    #endif  
    if(ret) {  
		goto out2;  
	}  
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */  
out2:  
	kfree(rxdata);					/* 释放内存 */  
out1:	  
	kfree(t);						/* 释放内存 */  
	return ret;  
}  
/*  
 * @description	: 向icm20608多个寄存器写入数据  
 * @param - dev:  icm20608设备  
 * @param - reg:  要写入的寄存器首地址  
 * @param - val:  要写入的数据缓冲区  
 * @param - len:  要写入的数据长度  
 * @return 	  :   操作结果  
 */  
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)  
{  
	int ret = -1;  
	unsigned char *txdata;  
	struct spi_message m;  
	struct spi_transfer *t;  
	struct spi_device *spi = (struct spi_device *)dev->private_data;  
	  
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */  
	if(!t) {  
		return -ENOMEM;  
	}  
	  
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);  
	if(!txdata) {  
		goto out1;  
	}  
	/* 一共发送len+1个字节的数据,第一个字节为  
	寄存器首地址,len为要写入的寄存器的集合,*/  
    #if 1  
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */  
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */  
	t->tx_buf = txdata;			/* 要发送的数据 */  
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */  
	spi_message_init(&m);		/* 初始化spi_message */  
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */  
	ret = spi_sync(spi, &m);	/* 同步发送 */  
    #else  
    //第一步,发送寄存器地址  
    txdata[0] = reg & ~0x80;  
    t->tx_buf = txdata;  
    t->len = 1;  
    spi_message_init(&m);  
	spi_message_add_tail(t, &m);  
	ret = spi_sync(spi, &m);  
      
    //第二步,写入数据  
    t->tx_buf = buf;  
    t->len = len;  
    spi_message_init(&m);  
	spi_message_add_tail(t, &m);  
	ret = spi_sync(spi, &m);  
    #endif  
    if(ret) {  
        goto out2;  
    }  
out2:  
	kfree(txdata);				/* 释放内存 */  
out1:  
	kfree(t);					/* 释放内存 */  
	return ret;  
}  
/*  
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器  
 * @param - dev:  icm20608设备  
 * @param - reg:  要读取的寄存器  
 * @return 	  :   读取到的寄存器值  
 */  
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) {  
	u8 data = 0;  
	icm20608_read_regs(dev, reg, &data, 1);  
	return data;  
}  
/*  
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器  
 * @param - dev:  icm20608设备  
 * @param - reg:  要写的寄存器  
 * @param - data: 要写入的值  
 * @return   :    无  
 */	  
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) {  
	u8 buf = value;  
	icm20608_write_regs(dev, reg, &buf, 1);  
}  
void icm20608_readdata(struct icm20608_dev *dev) {  
	unsigned char data[14] = { 0 };  
	icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);  
  
	dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);   
	dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);   
	dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);   
	dev->temp_adc    = (signed short)((data[6] << 8) | data[7]);   
	dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]);   
	dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);  
	dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);  
}  
  
  
static int icm20608_open(struct inode *inode, struct file *filp){  
	filp->private_data = &icm20608dev; /* 设置私有数据 */  
	return 0;  
}  
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){  
	signed int data[7];  
	long err = 0;  
	struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;  
  
	icm20608_readdata(dev);  
	data[0] = dev->gyro_x_adc;  
	data[1] = dev->gyro_y_adc;  
	data[2] = dev->gyro_z_adc;  
	data[3] = dev->accel_x_adc;  
	data[4] = dev->accel_y_adc;  
	data[5] = dev->accel_z_adc;  
	data[6] = dev->temp_adc;  
	err = copy_to_user(buf, data, sizeof(data));  
	return 0;  
}  
static int icm20608_release(struct inode *inode, struct file *filp){  
	return 0;  
}  
static const struct file_operations icm20608_ops = {  
	.owner = THIS_MODULE,  
	.open = icm20608_open,  
	.read = icm20608_read,  
	.release = icm20608_release,  
};  
  
void icm20608_reginit(void){  
	u8 value = 0;  
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);  
	mdelay(50);  
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);  
	mdelay(50);  
	value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);  
	printk("ICM20608 ID = %#X\r\n", value);	  
	icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/  
	icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/  
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/  
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/  
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/  
	icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/  
	icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/  
	icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/  
}	  
static int icm20608_probe(struct spi_device *spi){  
	if (icm20608dev.major) {  
		icm20608dev.devid = MKDEV(icm20608dev.major, 0);  
		register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);  
	} else {  
		alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);  
		icm20608dev.major = MAJOR(icm20608dev.devid);  
	}  
  
	cdev_init(&icm20608dev.cdev, &icm20608_ops);  
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);  
  
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);  
	if (IS_ERR(icm20608dev.class)) {  
		return PTR_ERR(icm20608dev.class);  
	}  
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);  
	if (IS_ERR(icm20608dev.device)) {  
		return PTR_ERR(icm20608dev.device);  
	}  
  
	/*初始化spi_device */  
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/  
	spi_setup(spi);  
	icm20608dev.private_data = spi; /* 设置私有数据 */  
	/* 初始化ICM20608内部寄存器 */  
	icm20608_reginit();		  
	return 0;  
}  
static int icm20608_remove(struct spi_device *spi) {  
	cdev_del(&icm20608dev.cdev);  
	unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);  
	device_destroy(icm20608dev.class, icm20608dev.devid);  
	class_destroy(icm20608dev.class);  
	return 0;  
}  
  
/* 传统匹配方式ID列表 */  
static const struct spi_device_id icm20608_id[] = {  
	{"alientek,icm20608", 0},    
	{}  
};  
  
/* 设备树匹配列表 */  
static const struct of_device_id icm20608_of_match[] = {  
	{ .compatible = "alientek,icm20608" },  
	{ /* Sentinel */ }  
};  
  
/* SPI驱动结构体 */	  
static struct spi_driver icm20608_driver = {  
	.probe = icm20608_probe,  
	.remove = icm20608_remove,  
	.driver = {  
			.owner = THIS_MODULE,  
		   	.name = "icm20608",  
		   	.of_match_table = icm20608_of_match,   
		   },  
	.id_table = icm20608_id,  
};  
		     
static int __init icm20608_init(void)  
{  
	return spi_register_driver(&icm20608_driver);  
}  
  
static void __exit icm20608_exit(void)  
{  
	spi_unregister_driver(&icm20608_driver);  
}  
  
module_init(icm20608_init);  
module_exit(icm20608_exit);  
MODULE_LICENSE("GPL");
1.3.2.2.2.1 驱动过程分析
  1. 作为一个spi从设备驱动,需要定义一个spi_driver icm20608_driver。通过spi_register_driver、spi_unregister_driver注册卸载。

  2. compatible匹配执行probe。

    1. 定一个icm20608_dev,按照字符设备框架,注册字符设备。
    2. spi_setup设置spi_device从设备, 设置spi设备的模式和速率。
      img
    3. 初始化ICM20608内部寄存器(发起spi传输)。
      1. 设置启动时序,使能读写。
      2. 读id。
      3. 设置量程,速率。
  3. ICM20608的读写

  4. icm20608_read调用icm20608_readdata,调用icm20608_read_regs

  5. icm20608_write_regs, icm20608_read_regs用来spi协议让主控去读写spi从设备,都是通过spi_sync进行数据传输。

img img

  将函数精简话一下:

img img 注意精简后有个bug, 就是调用spi_write后,又继续调用spi_read。这时imx6ul spi控制器驱动内部会帮忙控制cs片选信号。又会重新拉高,再拉低cs片选。因此导致数据传输异常。nxp官方也是用的gpio作为cs片选,软件手动去控制的。因此优化如下:

img img probe读出icm20608 id为:

img

1.3.2.2.3 icm20608App.c
#include "stdio.h"  
#include "unistd.h"  
#include "sys/types.h"  
#include "sys/stat.h"  
#include "sys/ioctl.h"  
#include "fcntl.h"  
#include "stdlib.h"  
#include "string.h"  
#include <poll.h>  
#include <sys/select.h>  
#include <sys/time.h>  
#include <signal.h>  
#include <fcntl.h>  
int main(int argc, char *argv[])  
{  
	int fd;  
	char *filename;  
	signed int databuf[7];  
	unsigned char data[14];  
	signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;  
	signed int accel_x_adc, accel_y_adc, accel_z_adc;  
	signed int temp_adc;  
  
	float gyro_x_act, gyro_y_act, gyro_z_act;  
	float accel_x_act, accel_y_act, accel_z_act;  
	float temp_act;  
	int ret = 0;  
  
	if (argc != 2) {  
		printf("Error Usage!\r\n");  
		return -1;  
	}  
	filename = argv[1];  
	fd = open(filename, O_RDWR);  
	if(fd < 0) {  
		printf("can't open file %s\r\n", filename);  
		return -1;  
	}  
	while (1) {  
		ret = read(fd, databuf, sizeof(databuf));  
		if(ret == 0) { 			/* 数据读取成功 */  
			gyro_x_adc = databuf[0];  
			gyro_y_adc = databuf[1];  
			gyro_z_adc = databuf[2];  
			accel_x_adc = databuf[3];  
			accel_y_adc = databuf[4];  
			accel_z_adc = databuf[5];  
			temp_adc = databuf[6];  
  
			/* 计算实际值 */  
			gyro_x_act = (float)(gyro_x_adc)  / 16.4;  
			gyro_y_act = (float)(gyro_y_adc)  / 16.4;  
			gyro_z_act = (float)(gyro_z_adc)  / 16.4;  
			accel_x_act = (float)(accel_x_adc) / 2048;  
			accel_y_act = (float)(accel_y_adc) / 2048;  
			accel_z_act = (float)(accel_z_adc) / 2048;  
			temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;  
			printf("\r\n原始值:\r\n");  
			printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);  
			printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);  
			printf("temp = %d\r\n", temp_adc);  
			printf("实际值:");  
			printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);  
			printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);  
			printf("act temp = %.2f°C\r\n", temp_act);  
		}  
		usleep(100000); /*100ms */  
	}  
	close(fd);	  
	return 0;  
}

测试结果:

img

1.3.3 SPI万能从设备驱动-spidev.c

万能SPI从设备驱动对应spidev, 驱动代码位于/drivers/spi/spidev.c。不用为每个spi从设备去写一个驱动,linux内核有一个万能通用的从设备驱动。

img 为什么说spidev.c是一个通用的从设备驱动。

spidev不是专门针对某一SPI硬件设备做的驱动,只是简单的注册字符设备,用户空间通过read、write、ioctl,直接对spi进行操作,相当于是用户空间和spi设备的桥梁。用户空间操作时,打开设备节点,然后通过IOCTL设置模式、速度等参数,然后就可以调用read write进行操作了。

1.3.3.1 spidev_init

img 注册了字符设备,主设备号SPIDEV_MAJOR= 153,绑定spidev_fops。

创建了spidev的class,创建完成后在用户空间/sys/class/下可以看到spidev目录结构。

spi_register_driver按照标准流程注册spidev从设备驱动。

1.3.3.2 spidev的probe

static const struct of_device_id spidev_dt_ids[] = {  
	{ .compatible = "rohm,dh2228fv" },  
	{ .compatible = "lineartechnology,ltc2488" },  
	{ .compatible = "ge,achc" },  
	{ .compatible = "nanopi,spidev" },  
	{ .compatible = "semtech,sx1301" },  
	{},  
};  
static struct spi_driver spidev_spi_driver = {  
	.driver = {  
		.name =		"spidev",  
		.of_match_table = of_match_ptr(spidev_dt_ids),  
		.acpi_match_table = ACPI_PTR(spidev_acpi_ids),  
	},  
	.probe =	spidev_probe,  
	.remove =	spidev_remove,  
};  
static int spidev_probe(struct spi_device *spi){  
	struct spidev_data	*spidev;  
	int			status;  
	unsigned long		minor;  
  
	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {  
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");  
		WARN_ON(spi->dev.of_node &&  
			!of_match_device(spidev_dt_ids, &spi->dev));  
	}  
  
	spidev_probe_acpi(spi);  
	/* Allocate driver data */  
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);  
	if (!spidev)  
		return -ENOMEM;  
	/* Initialize the driver data */  
	spidev->spi = spi;  
	spin_lock_init(&spidev->spi_lock);  
	mutex_init(&spidev->buf_lock);  
	INIT_LIST_HEAD(&spidev->device_entry);  
	/* If we can allocate a minor number, hook up this device.  
	 * Reusing minors is fine so long as udev or mdev is working.  
	 */  
	mutex_lock(&device_list_lock);  
	minor = find_first_zero_bit(minors, N_SPI_MINORS);  
	if (minor < N_SPI_MINORS) {  
		struct device *dev;  
		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);  
		dev = device_create(spidev_class, &spi->dev, spidev->devt,  
				    spidev, "spidev%d.%d",  
				    spi->master->bus_num, spi->chip_select);  
		status = PTR_ERR_OR_ZERO(dev);  
	} else {  
		dev_dbg(&spi->dev, "no minor number available!\n");  
		status = -ENODEV;  
	}  
	if (status == 0) {  
		set_bit(minor, minors);  
		list_add(&spidev->device_entry, &device_list);  
	}  
	mutex_unlock(&device_list_lock);  
	spidev->speed_hz = spi->max_speed_hz;  
	if (status == 0)  
		spi_set_drvdata(spi, spidev);  
	else  
		kfree(spidev);  
  
	return status;  
}

调用device_create创建了/dev/下的spidev节点,主设备号SPIDEV_MAJOR= 153,如spi总线0上cs1设备,则设备名为/dev/spidev0.1,其他以此类推。

1.3.3.3 spidev_fops

1.3.3.3.1 spidev_read
spidev_read  
	->spidev_sync_read  
		->spidev_sync  
			->spi_sync
1.3.3.3.2 spidev_write
spidev_write  
	->spidev_sync_write  
		->spidev_sync  
			->spi_sync

img 构造spi_message,spi_transfer调用spi_sync进行数据传输。

1.3.3.3.3 spidev_ioctl
static long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {  
	int			retval = 0;  
	struct spidev_data	*spidev;  
	struct spi_device	*spi;  
	u32			tmp;  
	unsigned		n_ioc;  
	struct spi_ioc_transfer	*ioc;  
    /* Check type and command number */  
    if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)  
        return -ENOTTY;  
    /* guard against device removal before, or while,  
     * we issue this ioctl.  
     */  
    spidev = filp->private_data;  
    spin_lock_irq(&spidev->spi_lock);  
    spi = spi_dev_get(spidev->spi);  
    spin_unlock_irq(&spidev->spi_lock);  
  
    if (spi == NULL)  
        return -ESHUTDOWN;  
  
    /* use the buffer lock here for triple duty:  
     *  - prevent I/O (from us) so calling spi_setup() is safe;  
     *  - prevent concurrent SPI_IOC_WR_* from morphing  
     *    data fields while SPI_IOC_RD_* reads them;  
     *  - SPI_IOC_MESSAGE needs the buffer locked "normally".  
     */  
    mutex_lock(&spidev->buf_lock);  
    switch (cmd) {  
    /* read requests */  
    case SPI_IOC_RD_MODE:  
        retval = put_user(spi->mode & SPI_MODE_MASK,  
                    (__u8 __user *)arg);  
        break;  
    case SPI_IOC_RD_MODE32:  
        retval = put_user(spi->mode & SPI_MODE_MASK,  
                    (__u32 __user *)arg);  
        break;  
    case SPI_IOC_RD_LSB_FIRST:  
        retval = put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,  
                    (__u8 __user *)arg);  
        break;  
    case SPI_IOC_RD_BITS_PER_WORD:  
        retval = put_user(spi->bits_per_word, (__u8 __user *)arg);  
        break;  
    case SPI_IOC_RD_MAX_SPEED_HZ:  
        retval = put_user(spidev->speed_hz, (__u32 __user *)arg);  
        break;  
  
    /* write requests */  
    case SPI_IOC_WR_MODE:  
    case SPI_IOC_WR_MODE32:  
        if (cmd == SPI_IOC_WR_MODE)  
            retval = get_user(tmp, (u8 __user *)arg);  
        else  
            retval = get_user(tmp, (u32 __user *)arg);  
        if (retval == 0) {  
            struct spi_controller *ctlr = spi->controller;  
            u32	save = spi->mode;  
  
            if (tmp & ~SPI_MODE_MASK) {  
                retval = -EINVAL;  
                break;  
            }  
  
            if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&  
                ctlr->cs_gpiods[spi->chip_select])  
                tmp |= SPI_CS_HIGH;  
  
            tmp |= spi->mode & ~SPI_MODE_MASK;  
            spi->mode = (u16)tmp;  
            retval = spi_setup(spi);  
            if (retval < 0)  
                spi->mode = save;  
            else  
                dev_dbg(&spi->dev, "spi mode %x\n", tmp);  
        }  
        break;  
    case SPI_IOC_WR_LSB_FIRST:  
        retval = get_user(tmp, (__u8 __user *)arg);  
        if (retval == 0) {  
            u32	save = spi->mode;  
  
            if (tmp)  
                spi->mode |= SPI_LSB_FIRST;  
            else  
                spi->mode &= ~SPI_LSB_FIRST;  
            retval = spi_setup(spi);  
            if (retval < 0)  
                spi->mode = save;  
            else  
                dev_dbg(&spi->dev, "%csb first\n",  
                        tmp ? 'l' : 'm');  
        }  
        break;  
    case SPI_IOC_WR_BITS_PER_WORD:  
        retval = get_user(tmp, (__u8 __user *)arg);  
        if (retval == 0) {  
            u8	save = spi->bits_per_word;  
  
            spi->bits_per_word = tmp;  
            retval = spi_setup(spi);  
            if (retval < 0)  
                spi->bits_per_word = save;  
            else  
                dev_dbg(&spi->dev, "%d bits per word\n", tmp);  
        }  
        break;  
    case SPI_IOC_WR_MAX_SPEED_HZ:  
        retval = get_user(tmp, (__u32 __user *)arg);  
        if (retval == 0) {  
            u32	save = spi->max_speed_hz;  
  
            spi->max_speed_hz = tmp;  
            retval = spi_setup(spi);  
            if (retval == 0) {  
                spidev->speed_hz = tmp;  
                dev_dbg(&spi->dev, "%d Hz (max)\n",  
                    spidev->speed_hz);  
            }  
            spi->max_speed_hz = save;  
        }  
        break;  
    default:  
        /* segmented and/or full-duplex I/O request */  
        /* Check message and copy into scratch area */  
        ioc = spidev_get_ioc_message(cmd,  
                (struct spi_ioc_transfer __user *)arg, &n_ioc);  
        if (IS_ERR(ioc)) {  
            retval = PTR_ERR(ioc);  
            break;  
        }  
        if (!ioc)  
            break;	/* n_ioc is also 0 */  
        /* translate to spi_message, execute */  
        retval = spidev_message(spidev, ioc, n_ioc);  
        kfree(ioc);  
        break;  
    }  
    mutex_unlock(&spidev->buf_lock);  
    spi_dev_put(spi);  
    return retval;  
}

img img 这些SPI_IOC命令就是一些设置速率参数,spi模式啊,然后就可以通过read,write操作/dev/spidev%d.%d设备了。

1.3.4 使用SPI万能驱动oled举例

1.3.4.1 spi oled原理

参考具体spi oled开发手册。

1.3.4.2 spi_oled.c

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include <string.h>  
#include <sys/ioctl.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <linux/types.h>  
#include <linux/spi/spidev.h>  
#include "font.h"  
static int fd_spidev;  
static int dc_pin_num;  
void OLED_DIsp_Set_Pos(int x, int y);  
void dc_pin_init(int number)  
{  
	// echo 509 > /sys/class/gpio/export  
	// echo out > /sys/class/gpio/gpio509/direction	  
	char cmd[100];  
	dc_pin_num = number;  
   
	sprintf(cmd, "echo %d > /sys/class/gpio/export", number);  
	system(cmd);  
	sprintf(cmd, "echo out > /sys/class/gpio/gpio%d/direction", number);  
	system(cmd);	  
}  
   
void oled_set_dc_pin(int val)  
{  
	/* echo 1 > /sys/class/gpio/gpio509/value  
	 * echo 0 > /sys/class/gpio/gpio509/value  
	 */  
	char cmd[100];  
	sprintf(cmd, "echo %d > /sys/class/gpio/gpio%d/value", val, dc_pin_num);  
	system(cmd);	  
}  
   
void spi_write_datas(const unsigned char *buf, int len)  
{  
	write(fd_spidev, buf, len);  
}  
   
void oled_write_datas(const unsigned char *buf, int len)  
{  
	oled_set_dc_pin(1);//拉高,表示写入数据  
	spi_write_datas(buf, len);  
}  
  			 		  						  					  				 	   		  	  	 	    
/**********************************************************************  
	 * 函数名称: oled_write_cmd  
	 * 功能描述: oled向特定地址写入数据或者命令  
	 * 输入参数:@uc_data :要写入的数据  
	 			@uc_cmd:为1则表示写入数据,为0表示写入命令  
	 * 输出参数:无  
	 * 返 回 值: 无  
 ***********************************************************************/  
void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)  
{  
	unsigned char uc_read=0;  
	if(uc_cmd==0)  
		oled_set_dc_pin(0);  
	else  
		oled_set_dc_pin(1);//拉高,表示写入数据  
  
	spi_write_datas(&uc_data, 1);//写入  
}  
int oled_init(void)  
{  
	unsigned char uc_dev_id = 0;  
		  			 		  						  					  				 	   		  	  	 	    
	oled_write_cmd_data(0xae,OLED_CMD);//关闭显示  
	oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address  
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address  
	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line  
	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address  
	oled_write_cmd_data(0x81,OLED_CMD);// contract control  
	oled_write_cmd_data(0x66,OLED_CMD);//128  
	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap  
	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse  
	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio  
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64  
	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction  
	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset  
	oled_write_cmd_data(0x00,OLED_CMD);//  
	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division  
	oled_write_cmd_data(0x80,OLED_CMD);//  
	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period  
	oled_write_cmd_data(0x1f,OLED_CMD);//  
	oled_write_cmd_data(0xda,OLED_CMD);//set com pins  
	oled_write_cmd_data(0x12,OLED_CMD);//  
	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh  
	oled_write_cmd_data(0x30,OLED_CMD);//  
	oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable   
	oled_write_cmd_data(0x14,OLED_CMD);//  
	oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on  
	return 0;  
}		  			 		  						  					  				 	   		  	  	 	    
/**********************************************************************  
	 * 函数名称: oled_fill_data  
	 * 功能描述: 整个屏幕显示填充某个固定数据  
	 * 输入参数:@fill_Data:要填充的数据  
 ***********************************************************************/  
int oled_fill_data(unsigned char fill_Data)  
{  
	unsigned char x,y;  
	for(x=0;x<8;x++) {  
		oled_write_cmd_data(0xb0+x,OLED_CMD);		//page0-page1  
		oled_write_cmd_data(0x00,OLED_CMD);		//low column start address  
		oled_write_cmd_data(0x10,OLED_CMD);		//high column start address	  
		for(y=0;y<128;y++)  
			oled_write_cmd_data(fill_Data,OLED_DATA);//填充数据				  
	}  
	return 0;  
}  
void OLED_DIsp_Clear(void)    
{  
    unsigned char x, y;  
	char buf[128];  
	memset(buf, 0, 128);  
    for (y = 0; y < 8; y++) {  
        OLED_DIsp_Set_Pos(0, y);  
        //for (x = 0; x < 128; x++)  
        //    oled_write_cmd_data(0, OLED_DATA); /* 清零 */  
        oled_write_datas(buf, 128);  
    }  
}  
   
/**********************************************************************  
	 * 函数名称: OLED_DIsp_All  
	 * 功能描述: 整个屏幕显示全部点亮,可以用于检查坏点  
 ***********************************************************************/  
void OLED_DIsp_All(void)    
{  
	unsigned char x, y;  
	for (y = 0; y < 8; y++)  
	{  
		OLED_DIsp_Set_Pos(0, y);  
		for (x = 0; x < 128; x++)  
			oled_write_cmd_data(0xff, OLED_DATA); /* 全点亮 */  
	}  
}  
   
//坐标设置  
/**********************************************************************  
	 * 函数名称: OLED_DIsp_Set_Pos  
	 * 功能描述:设置要显示的位置  
	 * 输入参数:@ x :要显示的column address  
	 			@y :要显示的page address  
 ***********************************************************************/  
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);  
}   	      	   			   
void OLED_DIsp_Char(int x, int y, unsigned char c)  
{  
	int i = 0;  
	/* 得到字模 */  
	const unsigned char *dots = oled_asc2_8x16[c - ' '];  
   
	/* 发给OLED */  
	OLED_DIsp_Set_Pos(x, y);  
	/* 发出8字节数据 */  
	//for (i = 0; i < 8; i++)  
	//	oled_write_cmd_data(dots[i], OLED_DATA);  
	oled_write_datas(&dots[0], 8);  
   
	OLED_DIsp_Set_Pos(x, y+1);  
	/* 发出8字节数据 */  
	//for (i = 0; i < 8; i++)  
		//oled_write_cmd_data(dots[i+8], OLED_DATA);  
	oled_write_datas(&dots[8], 8);  
}  
   
void OLED_DIsp_String(int x, int y, char *str)  
{  
	unsigned char j=0;  
	while (str[j]){		  
		OLED_DIsp_Char(x, y, str[j]);//显示单个字符  
		x += 8;  
		if(x > 127){  
			x = 0;  
			y += 2;  
		}//移动显示位置  
		j++;  
	}  
}  
  
void OLED_DIsp_Test(void)  
{ 	  
	int i;  
	OLED_DIsp_String(0, 0, "wiki.100ask.net");  
	OLED_DIsp_String(0, 2, "book.100ask.net");  
	OLED_DIsp_String(0, 4, "bbs.100ask.net");  
}   
   
/* spi_oled /dev/spidevB.D <DC_pin_number> */  
int main(int argc, char **argv)  
{  
	int dc_pin;  
	  
	if (argc != 3){  
		printf("Usage: %s <dev/spidevB.D> <DC_pin_number>\n", argv[0]);//B表示spi bus, D表示cs片选  
		return -1;  
	}  
   
	fd_spidev = open(argv[1], O_RDWR);  
	if (fd_spidev < 0) {  
		printf("open %s err\n", argv[1]);  
		return -1;  
	}  
   
	dc_pin = strtoul(argv[2], NULL, 0);  
	dc_pin_init(dc_pin);  
   
	oled_init();  
	OLED_DIsp_Clear();  
	OLED_DIsp_Test();  
	return 0;  
}
1.3.4.2.1 用户态驱动分析

有了spidev.c万能SPI从设备驱动,就不需要去写spi从设备驱动了,直接用户态去读写spidev即可。

  1. open("/dev/spi%d.%d", O_RDWR);//根据自己外设使用的spi bus和cs片选去设置
  2. 对外设oled进行初始化D/C(data or cmd )引脚, 利用gpio子系统的命令去设置gpio模式为输出。
  3. 利用spi协议发送初始化序列:
    1. spi_write_datas就是操作spidev,write数据到底层spidev,再调用对应的fops中的spidev_write,最终调用spi_sync传输数据。这里是每次传输1byte,把初始化序列利用spi协议写完。
  4. 清屏并且测试oled显示字符。

1.3.5 不使用SPI万能驱动oled举例

1.3.5.1 dts描述

&ecspi1 {  
    pinctrl-names = "default";  
    pinctrl-0 = <&pinctrl_ecspi1>;  
    fsl,spi-num-chipselects = <2>;  
    cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;  
    status = "okay";  
   
    oled: oled {  
        compatible = "100ask,oled";  
        reg = <0>;  
        spi-max-frequency = <10000000>;  
        dc-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>;   
    };  
};

spi1下接了一个spi oled,修改对应dts文件,修改ecspi1节点,添加oled子节点。oled有一个dc引脚,叫做data/cmd引脚,选择是发送数据还是命令。参考spi oled原理。

1.3.5.2 oled_drv.c

#include <linux/init.h>  
#include <linux/module.h>  
#include <linux/ioctl.h>  
#include <linux/fs.h>  
#include <linux/device.h>  
#include <linux/err.h>  
#include <linux/list.h>  
#include <linux/errno.h>  
#include <linux/mutex.h>  
#include <linux/slab.h>  
#include <linux/compat.h>  
#include <linux/of.h>  
#include <linux/of_device.h>  
#include <linux/acpi.h>  
#include <linux/spi/spi.h>  
#include <linux/spi/spidev.h>  
#include <linux/uaccess.h>  
#include <linux/gpio/consumer.h>  
   
#define OLED_IOC_INIT 			123  
#define OLED_IOC_SET_POS 		124  
   
//为0 表示命令,为1表示数据  
#define OLED_CMD 	0  
#define OLED_DATA 	1  
   
static struct spi_device *oled;  
static int major;  
static struct gpio_desc *dc_gpio;  
   
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);  
}  
  
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd) {  
	if(uc_cmd==0)  
		oled_set_dc_pin(0);  
	else  
		oled_set_dc_pin(1);//拉高,表示写入数据  
  
	spi_write_datas(&uc_data, 1);//写入  
}  
   
static int oled_init(void) {  
	oled_write_cmd_data(0xae,OLED_CMD);//关闭显示  
	oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address  
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address  
	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line  
	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address  
	oled_write_cmd_data(0x81,OLED_CMD);// contract control  
	oled_write_cmd_data(0x66,OLED_CMD);//128  
	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap  
	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse  
	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio  
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64  
	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction  
	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset  
	oled_write_cmd_data(0x00,OLED_CMD);//  
	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division  
	oled_write_cmd_data(0x80,OLED_CMD);//  
	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period  
	oled_write_cmd_data(0x1f,OLED_CMD);//  
	oled_write_cmd_data(0xda,OLED_CMD);//set com pins  
	oled_write_cmd_data(0x12,OLED_CMD);//  
	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh  
	oled_write_cmd_data(0x30,OLED_CMD);//  
	oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable   
	oled_write_cmd_data(0x14,OLED_CMD);//  
	oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on  
   
	return 0;  
}		  			 		  						  					  				 	   		  	  	 	    
   
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 long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {  
	int x, y;  
	/* 根据cmd操作硬件 */  
	switch (cmd) {  
		case OLED_IOC_INIT: /* init */  
		{  
			dc_pin_init();  
			oled_init();  
			break;  
		}  
		case OLED_IOC_SET_POS: /* set pos */  
		{  
			x = arg & 0xff;  
			y = (arg >> 8) & 0xff;  
			OLED_DIsp_Set_Pos(x, y);  
			break;  
		}  
	}  
	return 0;  
}  
   
static ssize_t spidev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {  
	char *ker_buf;  
	int err;  
	ker_buf = kmalloc(count, GFP_KERNEL);  
	err = copy_from_user(ker_buf, buf, count);  
	oled_set_dc_pin(1);//拉高,表示写入数据  
	spi_write_datas(ker_buf, count);  
	kfree(ker_buf);  
	return count;  
}  
   
static const struct file_operations spidev_fops = {  
	.owner =	THIS_MODULE,  
	.write =	spidev_write,  
	.unlocked_ioctl = spidev_ioctl,  
};  
   
static struct class *spidev_class;  
static const struct of_device_id spidev_dt_ids[] = {  
	{ .compatible = "100ask,oled" },  
	{},  
};  
  
static int spidev_probe(struct spi_device *spi)  
{  
	oled = spi;  
   
	major = register_chrdev(0, "100ask_oled", &spidev_fops);  
	spidev_class = class_create(THIS_MODULE, "100ask_oled");  
	device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");	  
   
	/* 3. 获得GPIO引脚 */  
	dc_gpio = gpiod_get(&spi->dev, "dc", 0);  
	return 0;  
}  
   
static int spidev_remove(struct spi_device *spi) {  
	gpiod_put(dc_gpio);  
	device_destroy(spidev_class, MKDEV(major, 0));  
	class_destroy(spidev_class);  
	unregister_chrdev(major, "cumtchw_oled");  
	return 0;  
}  
static struct spi_driver spidev_spi_driver = {  
	.driver = {  
		.name =		"cumtchw_spi_oled_drv",  
		.of_match_table = of_match_ptr(spidev_dt_ids),  
	},  
	.probe =	spidev_probe,  
	.remove =	spidev_remove,  
};  
static int __init spidev_init(void) {  
	int status;  
	status = spi_register_driver(&spidev_spi_driver);  
	if (status < 0) {  
	}  
	return status;  
}  
static void __exit spidev_exit(void){  
	spi_unregister_driver(&spidev_spi_driver);  
}  
module_init(spidev_init);  
module_exit(spidev_exit);  
MODULE_LICENSE("GPL");
1.3.5.2.1 驱动分析
  1. 调用spi_register_driver注册SPI从设备驱动,probe函数中利用标准字符设备驱动框架编写的驱动,注册字符设备,添加类。
  2. 当用户调用open("/dev/100ask_oled")后,就可以调用read,write函数读写oled。
    1. spidev_ioctl提供了2个ioctl命令,用来初始化oled和设置坐标位置。用spi_write_datas写入初始化序列,每次写1byte。
    2. spi_write_datas调用标准的SPI从设备驱动API(spi_write)传输数据。
  3. spi oled初始化完后就可以使用另一个OLED_IOC_SET_POS ioctl命令设置坐标位置。
  4. 调用spidev_write写入数据。

1.3.5.3 spi_oled.c应用测试

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include <string.h>  
#include <sys/ioctl.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <linux/types.h>  
#include <linux/spi/spidev.h>  
#include "font.h"  
#define OLED_IOC_INIT 			123  
#define OLED_IOC_SET_POS 		124  
   
//为0 表示命令,为1表示数据  
#define OLED_CMD 	0  
#define OLED_DATA 	1  
static int fd_spidev;  
static int dc_pin_num;  
void OLED_DIsp_Set_Pos(int x, int y);  
   
void oled_write_datas(const unsigned char *buf, int len)  
{  
	write(fd_spidev, buf, len);  
}  
void OLED_DIsp_Clear(void) {  
    unsigned char x, y;  
	char buf[128];  
	memset(buf, 0, 128);  
    for (y = 0; y < 8; y++) {  
        OLED_DIsp_Set_Pos(0, y);  
        oled_write_datas(buf, 128);  
    }  
}  
   
void OLED_DIsp_All(void)  {  
    unsigned char x, y;  
	char buf[128];  
	memset(buf, 0xff, 128);  
    for (y = 0; y < 8; y++) {  
        OLED_DIsp_Set_Pos(0, y);  
        oled_write_datas(buf, 128);  
    }  
}  
   
void OLED_DIsp_Set_Pos(int x, int y)  
{ 	  
	ioctl(fd_spidev, OLED_IOC_SET_POS, x  | (y << 8));  
}   	      	   			   
  
void OLED_DIsp_Char(int x, int y, unsigned char c)  
{  
	int i = 0;  
	/* 得到字模 */  
	const unsigned char *dots = oled_asc2_8x16[c - ' '];  
   
	/* 发给OLED */  
	OLED_DIsp_Set_Pos(x, y);  
	/* 发出8字节数据 */  
	//for (i = 0; i < 8; i++)  
	//	oled_write_cmd_data(dots[i], OLED_DATA);  
	oled_write_datas(&dots[0], 8);  
   
	OLED_DIsp_Set_Pos(x, y+1);  
	/* 发出8字节数据 */  
	//for (i = 0; i < 8; i++)  
		//oled_write_cmd_data(dots[i+8], OLED_DATA);  
	oled_write_datas(&dots[8], 8);  
}  
void OLED_DIsp_String(int x, int y, char *str)  
{  
	unsigned char j=0;  
	while (str[j]) {		  
		OLED_DIsp_Char(x, y, str[j]);//显示单个字符  
		x += 8;  
		if(x > 127) {  
			x = 0;  
			y += 2;  
		}//移动显示位置  
		j++;  
	}  
}  
void OLED_DIsp_Test(void) { 	  
	int i;  
	OLED_DIsp_String(0, 0, "100ask test");  
}   
  
int main(int argc, char **argv) {	  
	if (argc != 2) {  
		printf("Usage: %s /dev/cumtchw_oled\n", argv[0]);  
		return -1;  
	}  
   
	fd_spidev = open(argv[1], O_RDWR);  
	if (fd_spidev < 0) {  
		printf("open %s err\n", argv[1]);  
		return -1;  
	}  
	ioctl(fd_spidev, OLED_IOC_INIT);  
	OLED_DIsp_Clear();  
	OLED_DIsp_Test();  
	return 0;  
}

2 数据结构

2.1 spi_master/spi_controller

struct spi_master {  
	struct device	dev;	/* SPI设备的device数据结构 */  
	s16			bus_num;		/* SPI总线序号 */  
	u16			num_chipselect;	/* 片选信号数量 */  
	u16			dma_alignment;	/* SPI控制器DMA缓冲区对齐定义 */  
	u16			mode_bits;		/* 工作模式位,由驱动定义 */  
	u32			min_speed_hz;	/* 最小速度 */  
	u32			max_speed_hz;	/* 最小速度 */  
	u16			flags;			/* 限制条件标志 */  
	int			(*setup)(struct spi_device *spi);   /* 设置SPI设备的工作参数 */  
	int			(*transfer)(struct spi_device *spi,	/* SPI发送函数1 */  
						struct spi_message *mesg);  
	void		(*cleanup)(struct spi_device *spi);	/* SPI清除函数,当spi_master被释放时调用 */  
	int (*transfer_one_message)(struct spi_master *master,	/* SPI发送函数2 */  
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi,/* SPI发送函数3 */  
			    struct spi_transfer *transfer);  
	...  
};

struct spi_master描述一个spi控制器,包扩spi控制器的属性描述信息,spi的一些操作回调函数,如transfer,setup。

2.2 spi_driver

struct spi_driver {  
	const struct spi_device_id *id_table;  
	int			(*probe)(struct spi_device *spi);  
	int			(*remove)(struct spi_device *spi);  
	void			(*shutdown)(struct spi_device *spi);  
	struct device_driver	driver;  
};

用来描述一个spi从设备驱动。用来和spi从设备匹配。

2.3 spi_device

struct spi_device {  
	struct device		dev;  
	struct spi_controller	*controller;  
	struct spi_controller	*master;	/* compatibility layer */  
	u32			max_speed_hz;  
	u8			chip_select;  
#define	SPI_CPHA	0x01			/* clock phase */  
#define	SPI_CPOL	0x02			/* clock polarity */  
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */  
#define	SPI_MODE_1	(0|SPI_CPHA)  
#define	SPI_MODE_2	(SPI_CPOL|0)  
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)  
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */  
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */  
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */  
#define	SPI_LOOP	0x20			/* loopback mode */  
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */  
#define	SPI_READY	0x80			/* slave pulls low to pause */  
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */  
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */  
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */  
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */  
#define	SPI_CS_WORD	0x1000			/* toggle cs after each word */  
#define	SPI_TX_OCTAL	0x2000			/* transmit with 8 wires */  
#define	SPI_RX_OCTAL	0x4000			/* receive with 8 wires */  
#define	SPI_3WIRE_HIZ	0x8000			/* high impedance turnaround */  
	int			irq;  
	void			*controller_state;  
	void			*controller_data;  
	char			modalias[SPI_NAME_SIZE];  
	const char		*driver_override;  
	int			cs_gpio;	/* LEGACY: chip select gpio */  
	struct gpio_desc	*cs_gpiod;	/* chip select gpio desc */  
	struct spi_delay	word_delay; /* inter-word delay */  
};

用来描述一个spi从设备。用来和spi从设备驱动匹配。

2.4 spi_message/spi_transfer

struct spi_transfer {  
	const void	*tx_buf;  
	void		*rx_buf;  
	unsigned	len;  
	...  
	u8		bits_per_word;  
	u16		delay_usecs;  
	u32		speed_hz;  
	...  
	struct list_head transfer_list;  
};  
struct spi_message {  
	struct list_head	transfers;  
	...  
	struct spi_device	*spi;  
	...  
	void			(*complete)(void *context);  
	void			*context;  
	...  
	struct list_head	queue;  
};

spi_message是发起一次数据传输,里面的transfers构成基本输入输出数据,通过spi_sync函数或spi_async函数发送。