接上一篇讲了总线模型,总线从代码角度看都是虚拟概念,但是有的总线对应有实体硬件,有的没有。platform总线就没有实体硬件,这种称作虚拟总线。SPI,IIC这种有实体硬件的应该是真正的总线了=。=
总线的设计是为了代码的复用,其中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 *);
卸载驱动。
设备和驱动的匹配有四种模式
其他相关API在paltform_device.h中。下面是小例子。源码出自:https://blog.csdn.net/jklinux/article/details/73741523
#include
#include
#include
#include
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
#include
#include
#include
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_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;
};
简单使用填充好probe
和remove
这两个功能即可。驱动代码就不演示。
注册驱动
使用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
#include
#include
#include
#include
#include
#include
#include
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的架构跟SPI基本一样。
另外还有PCI总线、USB总线,话题太大以后再讲