接上一篇讲了总线模型,总线从代码角度看都是虚拟概念,但是有的总线对应有实体硬件,有的没有。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 *);卸载驱动。 设备和驱动的匹配有四种模式
通过设备树通过ACPI风格通过ID表(device设备名出现在driver的ID表里面就行)通过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_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 <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的架构跟SPI基本一样。
另外还有PCI总线、USB总线,话题太大以后再讲