# linux SPI驱动分析 linux下驱动都是分层结构,降低耦合度。 下面从 总线,设备, 驱动 分别分析linux下的SPI驱动。 ## 驱动目录分析 driver/spi下 spi-xxx芯片.c 芯片的spi控制器,如spi-sun6i.c spi-bitbang.c 位段 spi-gpio.c gpio模拟spi spi-slave-system-control.c 从设备 spi-slave-time.c 从设备 spi.c spidev.c 设备 ~~~ pure_initcall core_initcall core_initcall_sync postcore_initcall postcore_initcall_sync arch_initcall arch_initcall_sync subsys_initcall subsys_initcall_sync fs_initcall fs_initcall_sync rootfs_initcall device_initcall device_initcall_sync late_initcall late_initcall_sync ~~~ ### spi.c spi核心操作 postcore_initcall(spi_init); ~~~ buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); //用于SPI数据传输 …… status = bus_register(&spi_bus_type); // /sys/bus下注册总线 …… status = class_register(&spi_master_class); // /sys/class 下注册类 …… if (IS_ENABLED(CONFIG_SPI_SLAVE)) { status = class_register(&spi_slave_class); // /sys/class下注册从机类 …… } 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; ~~~ ~~~ struct bus_type spi_bus_type = { .name = "spi", .dev_groups = spi_dev_groups, .match = spi_match_device, //总线作用即是匹配设备与驱动 .uevent = spi_uevent, //发送总线事件提醒 }; static struct class spi_master_class = { .name = "spi_master", .owner = THIS_MODULE, .dev_release = spi_controller_release, .dev_groups = spi_master_groups, }; ~~~ #### 总线注册 设备和驱动都是挂载在总线上。 分析总线注册函数bus_register,首先申请了一个subsys_private结构体内存。 该结构体中包含了三个kset结构,分别是`struct kset subsys、struct kset *devices_kset和struct kset *drivers_kset。` 当每次设备插入,或者驱动加载,就会分配一个device或者device_drive结构,将其加入drivers或devices(kset结构)链表中。后面就可以通过总线来匹配设备和驱动了。 总线目录名为spi,即/sys/bus/spi。 匹配函数: ~~~ 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); /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) //驱动和设备匹配,则返回 return 1; if (sdrv->id_table) //id匹配 return !!spi_match_id(sdrv->id_table, spi); return strcmp(spi->modalias, drv->name) == 0; //别名匹配 } ~~~ #### spi统计接口 ~~~ static struct attribute *spi_device_statistics_attrs[] = { &dev_attr_spi_device_messages.attr, &dev_attr_spi_device_transfers.attr, &dev_attr_spi_device_errors.attr, &dev_attr_spi_device_timedout.attr, &dev_attr_spi_device_spi_sync.attr, &dev_attr_spi_device_spi_sync_immediate.attr, &dev_attr_spi_device_spi_async.attr, &dev_attr_spi_device_bytes.attr, &dev_attr_spi_device_bytes_rx.attr, &dev_attr_spi_device_bytes_tx.attr, &dev_attr_spi_device_transfer_bytes_histo0.attr, …… &dev_attr_spi_device_transfer_bytes_histo16.attr, &dev_attr_spi_device_transfers_split_maxsize.attr, } ~~~ #### 驱动相关 ~~~ static int spi_drv_probe(struct device *dev) //设置时钟频率,注册中断 int spi_add_device(struct spi_device *spi) //增加一个设备,设置模组,cs等,再调用父类device的方法 spi_unregister_device //最后调用实际控制器里的操作方法 spi_set_cs spi_map_buf spi_map_msg spi_transfer_one_message __spi_pump_messages spi_get_next_queued_message spi_finalize_current_message spi_start_queue spi_start_queue of_register_spi_device spi_register_controller devm_spi_register_controller spi_sync spi_write_then_read ~~~ ### spidev.c 一个通用的spidev设备驱动。 spi设备的操作。封装了spi.c里的东西。 设备匹配: ~~~ static const struct of_device_id spidev_dt_ids[] = { { .compatible = "rohm,dh2228fv" }, { .compatible = "lineartechnology,ltc2488" }, { .compatible = "ge,achc" }, { .compatible = "semtech,sx1301" }, {}, }; ~~~ ### gpio模拟spi spi-bitbang.c 位段形式的spi控制器 spi-gpio.c gpio模拟spi的具体实现,由spi-bitbang调用。 其它一些芯片的.c下也有实现txrx_word等,可以使用。 ~~~ //前面有一些具体的操作io函数 static inline void setsck(const struct spi_device *spi, int is_on) static inline void setmosi(const struct spi_device *spi, int is_on) static inline int getmiso(const struct spi_device *spi) //发送相关的调用了bitbang的函数 static u32 spi_gpio_txrx_word_mode0(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits) { return bitbang_txrx_be_cpha0(spi, nsecs, 0, 0, word, bits); } static const struct of_device_id spi_gpio_dt_ids[] = { { .compatible = "spi-gpio" }, {} }; MODULE_DEVICE_TABLE(of, spi_gpio_dt_ids); static int spi_gpio_probe_dt(struct platform_device *pdev) {} static struct platform_driver spi_gpio_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = of_match_ptr(spi_gpio_dt_ids), }, .probe = spi_gpio_probe, .remove = spi_gpio_remove, }; ~~~ ### 从机相关 spi-slave-system-control.c 从设备 spi-slave-time.c 从设备 ### 芯片spi控制器 以spi-sun6i.c为例 ~~~ struct sun6i_spi { struct spi_master *master; void __iomem *base_addr; struct clk *hclk; struct clk *mclk; struct reset_control *rstc; struct completion done; const u8 *tx_buf; u8 *rx_buf; int len; unsigned long fifo_depth; }; static const struct of_device_id sun6i_spi_match[] = { { .compatible = "allwinner,sun6i-a31-spi", .data = (void *)SUN6I_FIFO_DEPTH }, { .compatible = "allwinner,sun8i-h3-spi", .data = (void *)SUN8I_FIFO_DEPTH }, {} }; MODULE_DEVICE_TABLE(of, sun6i_spi_match); static const struct dev_pm_ops sun6i_spi_pm_ops = { .runtime_resume = sun6i_spi_runtime_resume, .runtime_suspend = sun6i_spi_runtime_suspend, }; //填充spi_master的参数 static int sun6i_spi_probe(struct platform_device *pdev) { sspi->master = master; sspi->fifo_depth = (unsigned long)of_device_get_match_data(&pdev->dev); master->max_speed_hz = 100 * 1000 * 1000; master->min_speed_hz = 3 * 1000; master->set_cs = sun6i_spi_set_cs; master->transfer_one = sun6i_spi_transfer_one; master->num_chipselect = 4; master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; master->bits_per_word_mask = SPI_BPW_MASK(8); master->dev.of_node = pdev->dev.of_node; master->auto_runtime_pm = true; master->max_transfer_size = sun6i_spi_max_transfer_size; sspi->hclk = devm_clk_get(&pdev->dev, "ahb"); } static struct platform_driver sun6i_spi_driver = { .probe = sun6i_spi_probe, .remove = sun6i_spi_remove, .driver = { .name = "sun6i-spi", .of_match_table = sun6i_spi_match, .pm = &sun6i_spi_pm_ops, }, }; module_platform_driver(sun6i_spi_driver); /* module_platform_driver(xxx); 最终展开后就是如下形式: static int __init xxx_init(void) { return platform_driver_register(&xxx); } module_init(xxx_init); static void __exit xxx_init(void) { return platform_driver_unregister(&xxx); } module_exit(xxx_exit); */ ~~~