32.【linux驱动】一文读懂linux设备驱动模型(常见总线bus)

总线分类

接上一篇讲了总线模型,总线从代码角度看都是虚拟概念,但是有的总线对应有实体硬件,有的没有。platform总线就没有实体硬件,这种称作虚拟总线。SPI,IIC这种有实体硬件的应该是真正的总线了=。=

platform总线

总线的设计是为了代码的复用,其中platform总线是最经常使用的虚拟总线,任何直接与CPU打交道的设备都挂接在platform虚拟总线上。
platform总线已经实现好的,只需要使用。使用的时候需要填充注册platform_device设备和platform_driver驱动。

struct platform_device {
	const char	* name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
	const struct platform_device_id	*id_entry;
	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};

填充完必要的参数使用int platform_device_add(struct platform_device *pdev);注册设备使用platform_device_del(struct platform_device *pdev);删除设备。

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
};

填充完必要的参数使用int platform_driver_register(struct platform_driver *);注册驱动。使用void platform_driver_unregister(struct platform_driver *);卸载驱动。
设备和驱动的匹配有四种模式

  1. 通过设备树
  2. 通过ACPI风格
  3. 通过ID表(device设备名出现在driver的ID表里面就行)
  4. 通过device和driver的名字

其他相关API在paltform_device.h中。下面是小例子。源码出自:https://blog.csdn.net/jklinux/article/details/73741523


#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>


struct platform_device mydev = {
    .name = "distancer",
    .id = 0,
    .dev = {
        .platform_data = NULL,
    },
    .resource = NULL,
};

//生成初始化函数和卸载函数,并用module_init, module_exit指定相应的函数
module_driver(mydev, platform_device_register, platform_device_unregister);

MODULE_LICENSE("GPL");

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>

int myprobe(struct platform_device *pdev)
{
    printk("in myprobe : %s.%d, driver_data=%p\n", pdev->name, pdev->id, pdev->id_entry->driver_data);
    return 0;
}

int myremove(struct platform_device *pdev)
{
    printk("in myremove : %s.%d, driver_data=%p\n", pdev->name, pdev->id, pdev->id_entry->driver_data);
    return 0;
}

struct platform_device_id ids[] = {
    {"mykey", 0x11},
    {"myir",  0x22},
    {"distancer", 0x33},
    {}, //最后必须给一个空的元素,标明是最后一个
};  

struct platform_driver mypdrv = {
    .probe = myprobe,
    .remove = myremove, 

    .driver = {
        .owner = THIS_MODULE,
        .name = "mydrv",    
    },
    .id_table = ids,
};

module_platform_driver(mypdrv);

MODULE_LICENSE("GPL");
SPI总线

为了实现代码的复用,将外设与总线控制驱动分离。SPI驱动架构中存在SPI总线,SPI控制器设备,SPI控制器驱动,SPI外设,SPI外设驱动这几个概念。SPI控制器设备描述了SPI控制器的信息,SPI控制器驱动加载后会提供SPI读写操作函数,SPI外设驱动只需要使用这些读写函数即可,SPI外设描述了使用那个SPI控制器。这样可以实现外设驱动不依赖SPI控制器,即时换了一个平台,外设驱动也不需要改一行代码。SPI总线对于SPI外设驱动而言就是一条通信管道。SPI控制器驱动好了之后对应在内核中生成spi_master对象。借用网上一张图片
在这里插入图片描述

外设驱动怎样写

基本结构
先看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);
	int			(*suspend)(struct spi_device *spi, pm_message_t mesg);
	int			(*resume)(struct spi_device *spi);
	struct device_driver	driver;
};

简单使用填充好proberemove这两个功能即可。驱动代码就不演示。

注册驱动
使用int spi_register_driver(struct spi_driver *sdrv);将上面的结构体注册到内核即可。
与外设通信
常用函数有:
int spi_write(struct spi_device *spi, const void *buf, size_t len);
int spi_read(struct spi_device *spi, void *buf, size_t len);
这两个函数都需要spi_device,在这个结构体里面就保存了使用那个控制器(spi_master),这个spi_device在probe函数被传进来的,需要保存下来。

注册设备
一般情况下spi设备驱动都是在开始的时候使用板级注册函数spi_register_board_info就注册好了,开机之后不让注册。spi_register_board_info没有被导出,无法使用。但是先看一下spi_board_info结构体描述外设。

struct spi_board_info {
	char		modalias[SPI_NAME_SIZE];//匹配名
	const void	*platform_data;			//自定义数据
	void		*controller_data;		//使用那个spi控制器
	int		irq;						//中断号
	u32		max_speed_hz;				//最大速度
	u16		bus_num;					//使用那个控制器
	u16		chip_select;				//片选
	u8		mode;						//模式
};

下面是一个实例。

struct pl022_config_chip spi0_info = {
	/* available POLLING_TRANSFER, INTERRUPT_TRANSFER, DMA_TRANSFER */
	.com_mode = CFG_SPI0_COM_MODE,
	.iface = SSP_INTERFACE_MOTOROLA_SPI,
	/* We can only act as master but SSP_SLAVE is possible in theory */
	.hierarchy = SSP_MASTER,
	/* 0 = drive TX even as slave, 1 = do not drive TX as slave */
	.slave_tx_disable = 1,
	.rx_lev_trig = SSP_RX_4_OR_MORE_ELEM,
	.tx_lev_trig = SSP_TX_4_OR_MORE_EMPTY_LOC,
	.ctrl_len = SSP_BITS_8,
	.wait_state = SSP_MWIRE_WAIT_ZERO,
	.duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX,
#if (CFG_SPI0_CS_GPIO_MODE)
	.cs_control = spi0_cs,
#endif
	.clkdelay = SSP_FEEDBACK_CLK_DELAY_1T,
};

static struct spi_board_info spi_plat_board[] __initdata = {
	[0] = {
		.modalias        = "spidev",    /* fixup */
		.max_speed_hz    = 3125000,     /* max spi clock (SCK) speed in HZ */
		.bus_num         = 0,           /* Note> set bus num, must be smaller than ARRAY_SIZE(spi_plat_device) */
		.chip_select     = 0,           /* Note> set chip select num, must be smaller than spi cs_num */
		.controller_data = &spi0_info,
		.mode            = SPI_MODE_3 | SPI_CPOL | SPI_CPHA,
	},
};

上面的参数比较复杂,留着后面参数。下面解决spi_register_board_info函数不能用的问题。看其源码

int __devinit
spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
	struct boardinfo *bi;
	int i;

	bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
	if (!bi)
		return -ENOMEM;

	for (i = 0; i < n; i++, bi++, info++) {
		struct spi_master *master;

		memcpy(&bi->board_info, info, sizeof(*info));
		mutex_lock(&board_lock);
		list_add_tail(&bi->list, &board_list);
		list_for_each_entry(master, &spi_master_list, list)
			spi_match_master_to_boardinfo(master, &bi->board_info);
		mutex_unlock(&board_lock);
	}

	return 0;
}

重点在于拿到spi_master这个控制器设备就可以另辟蹊径注册设备,还好内核有给出他的获取函数spi_busnum_to_master,参考其他设备的写法得出注册方法。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <mach/gpio.h>
#include <mach/platform.h>
#include <linux/spi/spi.h>
#include <linux/amba/pl022.h>

static struct fb_data fb1_plat_data = {

};

struct pl022_config_chip spi0_info = {
	/* available POLLING_TRANSFER, INTERRUPT_TRANSFER, DMA_TRANSFER */
	.com_mode = CFG_SPI0_COM_MODE,
	.iface = SSP_INTERFACE_MOTOROLA_SPI,
	/* We can only act as master but SSP_SLAVE is possible in theory */
	.hierarchy = SSP_MASTER,
	/* 0 = drive TX even as slave, 1 = do not drive TX as slave */
	.slave_tx_disable = 1,
	.rx_lev_trig = SSP_RX_4_OR_MORE_ELEM,
	.tx_lev_trig = SSP_TX_4_OR_MORE_EMPTY_LOC,
	.ctrl_len = SSP_BITS_8,
	.wait_state = SSP_MWIRE_WAIT_ZERO,
	.duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX,
	.clkdelay = SSP_FEEDBACK_CLK_DELAY_1T,
};

static struct spi_board_info spi_plat_board = {
	.modalias        = "hello_spi_fb",    /* fixup */
	.max_speed_hz    = 25000000,     /* max spi clock (SCK) speed in HZ */
	.bus_num         = 0,           /* Note> set bus num, must be smaller than ARRAY_SIZE(spi_plat_device) */
	.chip_select     = 1,           /* Note> set chip select num, must be smaller than spi cs_num */
	.controller_data = &spi0_info,
	.mode            = SPI_MODE_3 | SPI_CPOL | SPI_CPHA,
	.platform_data   = &fb1_plat_data,
};

__attribute__ ((unused)) static void device_spi_delete(struct spi_master *master, unsigned cs)
{
	struct device *dev;
	char str[32];

	snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs);

	dev = bus_find_device_by_name(&spi_bus_type, NULL, str);
	if (dev) {
		printk(": Deleting %s\n", str);
		device_del(dev);
	}
}

static struct spi_device *spi_device;
static int __init hello_init(void) {
	struct spi_master *master;

	master = spi_busnum_to_master(spi_plat_board.bus_num);
	if (!master) {
		printk(":  spi_busnum_to_master(%d) returned NULL\n",
		       spi_plat_board.bus_num);
		return -EINVAL;
	}
	/* make sure it's available */
	device_spi_delete(master, spi_plat_board.chip_select);
	spi_device = spi_new_device(master, &spi_plat_board);
	put_device(&master->dev);
	if (!spi_device) {
		printk(":    spi_new_device() returned NULL\n");
		return -EPERM;
	}
	return 0;
	printk("hello device init\n");
	return 0;
}

static void __exit hello_exit(void) {
	if (spi_device) {
		if (spi_device->master->cleanup) {
			spi_device->master->cleanup(spi_device);
		}
		device_del(&spi_device->dev);
		kfree(spi_device);
	}
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

最后整个流程如下
在这里插入图片描述
图片来自https://www.cnblogs.com/multimicro/p/11726863.html

主机驱动结构

外设驱动已经讲完,拆解一下主机控制器的驱动结构。
spi控制器驱动核心部分是
linux/spi/spi.h
drivers/spi/spi.c
这里面封装了SPI总线的操作方式,例如数据的读写,定义了spi_bus_type总线,注册控制器驱动、外设、外设驱动。这里面主要牵涉到三个概念spi_master(spi控制器),spi_device,spi_driver。spi总线除了匹配spi_device和spi_driver以外还需要匹配spi_master。spi_master就是spi控制器驱动了,只需要填充并注册这个就可以。看一下它的结构:

struct spi_master {
	struct device	dev;
	struct list_head list;
	s16			bus_num;
	u16			num_chipselect;
	u16			dma_alignment;
	u16			mode_bits;
	u16			flags;
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;
	bool			bus_lock_flag;
	int			(*setup)(struct spi_device *spi);
	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);
	void			(*cleanup)(struct spi_device *spi);
	bool				queued;
	struct kthread_worker		kworker;
	struct task_struct		*kworker_task;
	struct kthread_work		pump_messages;
	spinlock_t			queue_lock;
	struct list_head		queue;
	struct spi_message		*cur_msg;
	bool				busy;
	bool				running;
	bool				rt;

	int (*prepare_transfer_hardware)(struct spi_master *master);
	int (*transfer_one_message)(struct spi_master *master,
				    struct spi_message *mesg);
	int (*unprepare_transfer_hardware)(struct spi_master *master);
};

SPI相关的所有操作都定义在这里面了,只需要实现好功能注册这个结构体即可,以片内外设驱动pl022为例看一下他的注册过程:
drivers/spi/spi-pl022.c

	/* Allocate master with space for data */
	master = spi_alloc_master(dev, sizeof(struct pl022));
	if (master == NULL) {
		dev_err(&adev->dev, "probe - cannot alloc SPI master\n");
		status = -ENOMEM;
		goto err_no_master;
	}

	pl022 = spi_master_get_devdata(master);
	pl022->master = master;
	pl022->master_info = platform_info;
	pl022->adev = adev;
	pl022->vendor = id->data;

	/*
	 * Bus Number Which has been Assigned to this SSP controller
	 * on this board
	 */
	master->bus_num = platform_info->bus_id;
	master->num_chipselect = platform_info->num_chipselect;
	master->cleanup = pl022_cleanup;
	master->setup = pl022_setup;
	master->prepare_transfer_hardware = pl022_prepare_transfer_hardware;
	master->transfer_one_message = pl022_transfer_one_message;
	master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
	master->rt = platform_info->rt;

	/*
	 * Supports mode 0-3, loopback, and active low CS. Transfers are
	 * always MS bit first on the original pl022.
	 */
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
	if (pl022->vendor->extended_cr)
		master->mode_bits |= SPI_LSB_FIRST;

	dev_dbg(&adev->dev, "BUSNO: %d\n", master->bus_num);

	status = amba_request_regions(adev, NULL);
	if (status)
		goto err_no_ioregion;

	pl022->phybase = adev->res.start;
	pl022->virtbase = ioremap(adev->res.start, resource_size(&adev->res));
	if (pl022->virtbase == NULL) {
		status = -ENOMEM;
		goto err_no_ioremap;
	}
	printk(KERN_INFO "pl022: mapped registers from 0x%08x to %p\n",
	       adev->res.start, pl022->virtbase);

	pl022->clk = clk_get(&adev->dev, NULL);
	platform_info->init(master->bus_num);		/*bok add func */

	if (IS_ERR(pl022->clk)) {
		status = PTR_ERR(pl022->clk);
		dev_err(&adev->dev, "could not retrieve SSP/SPI bus clock\n");
		goto err_no_clk;
	}

	status = clk_prepare(pl022->clk);
	if (status) {
		dev_err(&adev->dev, "could not prepare SSP/SPI bus clock\n");
		goto  err_clk_prep;
	}
	
	status = clk_enable(pl022->clk);
	if (status) {
		dev_err(&adev->dev, "could not enable SSP/SPI bus clock\n");
		goto err_no_clk_en;
	}

	/* Initialize transfer pump */
	tasklet_init(&pl022->pump_transfers, pump_transfers,
		     (unsigned long)pl022);

	/* Disable SSP */
	writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)),
	       SSP_CR1(pl022->virtbase));
	load_ssp_default_config(pl022);

	status = request_irq(adev->irq[0], pl022_interrupt_handler, 0, "pl022",
			     pl022);
	if (status < 0) {
		dev_err(&adev->dev, "probe - cannot get IRQ (%d)\n", status);
		goto err_no_irq;
	}

	/* Get DMA channels */
	if (platform_info->enable_dma) {
		status = pl022_dma_probe(pl022);
		if (status != 0)
			platform_info->enable_dma = 0;
	}

	/* Register with the SPI framework */
	amba_set_drvdata(adev, pl022);
	status = spi_register_master(master);

截取了pl022的probe过程中的关键部分,pl022是注册在amba总线上的,这个对spi驱动结构没有影响,注册到哪里都一样。我删除了无关信息。第一句初始化了一个master结构体,最后使用spi_register_master就完成了spi_master的注册,之后外设想要使用这个总线就可以在spi.c里面拿到并且使用。

I2C总线

I2C的架构跟SPI基本一样。

另外还有PCI总线、USB总线,话题太大以后再讲

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值