×

Linux内核之SPI协议

admin admin 发表于2024-05-07 16:57:03 浏览8 评论0

抢沙发发表评论

SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行的行业标准,但是并没有像I2C那样有标准文档,它还有主从、可片选的特性。

图源自Serial Peripheral Interface-wikipedia

时序图 放个经典老图,来源未知。相位和极性决定了采样点,主从采样点一致时数据正确,不一致时会导致数据错误但硬件自己其实无法察觉。

作为一种事实标准,SPI还衍生出众多的总线协议:eSPI DSPI QSPI QPI,以及仅有MOSI\SCLK两根线的单工SPI,甚至有的还有DTR双边采样特性。

应用场景:

读写寄存器 中型数据传输(速率一般为1MHz~20MHz) EEPROM、FLASH等储存器件 一些固件升级

Linux

文档:https://www.kernel.org/doc/html/v5.14/spi/index.html 源码:drivers/spi

分层没有I2C那样好,主打的就是一堆,主打能用就行。一个目录放满...

头文件 include/linux/spi/

公共的结构体位于 include/linux/spi/spi.h

spi_controller 结构体(SPI控制器实例)

关键的收发函数指针都在这里。这个结构体会由 spi-xxxx.c 控制器驱动进行初始化。

c
/** * struct spi_controller - interface to SPI master or slave controller,控制器接口(主从皆可) * @dev: device interface to this driver,可以用spi_controller_get_devdata(ctlr)获取 * @list: link with the global spi_controller list * @bus_num: board-specific (and often SOC-specific) identifier for a * given SPI controller. 被SoC系统分配的总线序号 * @num_chipselect: chipselects are used to distinguish individual * SPI slaves, and are numbered from zero to num_chipselects. 片选编号,即spi地址 * each slave has a chipselect signal, but its common that not * every chipselect is connected to a slave. * @dma_alignment: SPI controller constraint on DMA buffers alignment. * @mode_bits: flags understood by this controller driver * @buswidth_override_bits: flags to override for this controller driver * @bits_per_word_mask: A mask indicating which values of bits_per_word are * supported by the driver. Bit n indicates that a bits_per_word n+1 is * supported. If set, the SPI core will reject any transfer with an * unsupported bits_per_word. If not set, this value is simply ignored, * and its up to the individual driver to perform any validation. * @min_speed_hz: Lowest supported transfer speed * @max_speed_hz: Highest supported transfer speed * @flags: other constraints relevant to this driver * @slave: indicates that this is an SPI slave controller * @target: indicates that this is an SPI target controller * @devm_allocated: whether the allocation of this struct is devres-managed * @max_transfer_size: function that returns the max transfer size for * a &spi_device; may be %NULL, so the default %SIZE_MAX will be used. * @max_message_size: function that returns the max message size for * a &spi_device; may be %NULL, so the default %SIZE_MAX will be used. * @io_mutex: mutex for physical bus access * @add_lock: mutex to avoid adding devices to the same chipselect * @bus_lock_spinlock: spinlock for SPI bus locking * @bus_lock_mutex: mutex for exclusion of multiple callers * @bus_lock_flag: indicates that the SPI bus is locked for exclusive use * @setup: updates the device mode and clocking records used by a * devices SPI controller; protocol code may call this. This * must fail if an unrecognized or unsupported mode is requested. * Its always safe to call this unless transfers are pending on * the device whose settings are being modified. * @set_cs_timing: optional hook for SPI devices to request SPI master * controller for configuring specific CS setup time, hold time and inactive * delay interms of clock counts * @transfer: adds a message to the controllers transfer queue. * @cleanup: frees controller-specific state * @can_dma: determine whether this controller supports DMA * @dma_map_dev: device which can be used for DMA mapping * @cur_rx_dma_dev: device which is currently used for RX DMA mapping * @cur_tx_dma_dev: device which is currently used for TX DMA mapping * @queued: whether this controller is providing an internal message queue * @kworker: pointer to thread struct for message pump * @pump_messages: work struct for scheduling work to the message pump * @queue_lock: spinlock to synchronise access to message queue * @queue: message queue * @cur_msg: the currently in-flight message * @cur_msg_completion: a completion for the current in-flight message * @cur_msg_incomplete: Flag used internally to opportunistically skip * the @cur_msg_completion. This flag is used to check if the driver has * already called spi_finalize_current_message(). * @cur_msg_need_completion: Flag used internally to opportunistically skip * the @cur_msg_completion. This flag is used to signal the context that * is running spi_finalize_current_message() that it needs to complete() * @cur_msg_mapped: message has been mapped for DMA * @fallback: fallback to PIO if DMA transfer return failure with * SPI_TRANS_FAIL_NO_START. * @last_cs_mode_high: was (mode & SPI_CS_HIGH) true on the last call to set_cs. * @last_cs: the last chip_select that is recorded by set_cs, -1 on non chip * selected * @xfer_completion: used by core transfer_one_message() * @busy: message pump is busy * @running: message pump is running * @rt: whether this queue is set to run as a realtime task * @auto_runtime_pm: the core should ensure a runtime PM reference is held * while the hardware is prepared, using the parent * device for the spidev * @max_dma_len: Maximum length of a DMA transfer for the device. * @prepare_transfer_hardware: a message will soon arrive from the queue * so the subsystem requests the driver to prepare the transfer hardware * by issuing this call * @transfer_one_message: the subsystem calls the driver to transfer a single * message while queuing transfers that arrive in the meantime. When the * driver is finished with this message, it must call * spi_finalize_current_message() so the subsystem can issue the next * message * @unprepare_transfer_hardware: there are currently no more messages on the * queue so the subsystem notifies the driver that it may relax the * hardware by issuing this call * * @set_cs: set the logic level of the chip select line. May be called * from interrupt context. * @optimize_message: optimize the message for reuse * @unoptimize_message: release resources allocated by optimize_message * @prepare_message: set up the controller to transfer a single message, * for example doing DMA mapping. Called from threaded * context. * @transfer_one: transfer a single spi_transfer. * * - return 0 if the transfer is finished, * - return 1 if the transfer is still in progress. When * the driver is finished with this transfer it must * call spi_finalize_current_transfer() so the subsystem * can issue the next transfer. If the transfer fails, the * driver must set the flag SPI_TRANS_FAIL_IO to * spi_transfer->error first, before calling * spi_finalize_current_transfer(). * Note: transfer_one and transfer_one_message are mutually * exclusive; when both are set, the generic subsystem does * not call your transfer_one callback. * @handle_err: the subsystem calls the driver to handle an error that occurs * in the generic implementation of transfer_one_message(). * @mem_ops: optimized/dedicated operations for interactions with SPI memory. * This field is optional and should only be implemented if the * controller has native support for memory like operations. * @mem_caps: controller capabilities for the handling of memory operations. * @unprepare_message: undo any work done by prepare_message(). * @slave_abort: abort the ongoing transfer request on an SPI slave controller * @target_abort: abort the ongoing transfer request on an SPI target controller * @cs_gpiods: Array of GPIO descriptors to use as chip select lines; one per CS * number. Any individual value may be NULL for CS lines that * are not GPIOs (driven by the SPI controller itself). * @use_gpio_descriptors: Turns on the code in the SPI core to parse and grab * GPIO descriptors. This will fill in @cs_gpiods and SPI devices will have * the cs_gpiod assigned if a GPIO line is found for the chipselect. * @unused_native_cs: When cs_gpiods is used, spi_register_controller() will * fill in this field with the first unused native CS, to be used by SPI * controller drivers that need to drive a native CS when using GPIO CS. * @max_native_cs: When cs_gpiods is used, and this field is filled in, * spi_register_controller() will validate all native CS (including the * unused native CS) against this value. * @pcpu_statistics: statistics for the spi_controller * @dma_tx: DMA transmit channel * @dma_rx: DMA receive channel * @dummy_rx: dummy receive buffer for full-duplex devices * @dummy_tx: dummy transmit buffer for full-duplex devices * @fw_translate_cs: If the boot firmware uses different numbering scheme * what Linux expects, this optional hook can be used to translate * between the two. * @ptp_sts_supported: If the driver sets this to true, it must provide a * time snapshot in @spi_transfer->ptp_sts as close as possible to the * moment in time when @spi_transfer->ptp_sts_word_pre and * @spi_transfer->ptp_sts_word_post were transmitted. * If the driver does not set this, the SPI core takes the snapshot as * close to the driver hand-over as possible. * @irq_flags: Interrupt enable state during PTP system timestamping * @queue_empty: signal green light for opportunistically skipping the queue * for spi_sync transfers. * @must_async: disable all fast paths in the core * * Each SPI controller can communicate with one or more @spi_device * children. These make a small bus, sharing MOSI, MISO and SCK signals * but not chip select signals. Each device may be configured to use a * different clock rate, since those shared signals are ignored unless * the chip is selected. * * The driver for an SPI controller manages access to those devices through * a queue of spi_message transactions, copying data between CPU memory and * an SPI slave device. For each such message it queues, it calls the * messages completion function when the transaction completes. */ struct spi_controller { struct device dev; struct list_head list; /* * Other than negative (== assign one dynamically), bus_num is fully * board-specific. Usually that simplifies to being SoC-specific. * example: one SoC has three SPI controllers, numbered 0..2, * and one boards schematics might show it using SPI-2. Software * would normally use bus_num=2 for that controller. */ s16 bus_num; /* * Chipselects will be integral to many controllers; some others * might use board-specific GPIOs. */ u16 num_chipselect; /* Some SPI controllers pose alignment requirements on DMAable * buffers; let protocol drivers know about these requirements. */ u16 dma_alignment; /* spi_device.mode flags understood by this controller driver */ u32 mode_bits; /* spi_device.mode flags override flags for this controller */ u32 buswidth_override_bits; /* Bitmask of supported bits_per_word for transfers */ u32 bits_per_word_mask; #define SPI_BPW_MASK(bits) BIT((bits) - 1) #define SPI_BPW_RANGE_MASK(min, max) GENMASK((max) - 1, (min) - 1) /* Limits on transfer speed */ u32 min_speed_hz; u32 max_speed_hz; /* Other constraints relevant to this driver */ u16 flags; #define SPI_CONTROLLER_HALF_DUPLEX BIT(0) /* Cant do full duplex */ #define SPI_CONTROLLER_NO_RX BIT(1) /* Cant do buffer read */ #define SPI_CONTROLLER_NO_TX BIT(2) /* Cant do buffer write */ #define SPI_CONTROLLER_MUST_RX BIT(3) /* Requires rx */ #define SPI_CONTROLLER_MUST_TX BIT(4) /* Requires tx */ #define SPI_CONTROLLER_GPIO_SS BIT(5) /* GPIO CS must select slave */ #define SPI_CONTROLLER_SUSPENDED BIT(6) /* Currently suspended */ /* * The spi-controller has multi chip select capability and can * assert/de-assert more than one chip select at once. */ #define SPI_CONTROLLER_MULTI_CS BIT(7) /* Flag indicating if the allocation of this struct is devres-managed */ bool devm_allocated; union { /* Flag indicating this is an SPI slave controller */ bool slave; /* Flag indicating this is an SPI target controller */ bool target; }; /* * On some hardware transfer / message size may be constrained * the limit may depend on device transfer settings. */ size_t (*max_transfer_size)(struct spi_device *spi); size_t (*max_message_size)(struct spi_device *spi); /* I/O mutex */ struct mutex io_mutex; /* Used to avoid adding the same CS twice */ struct mutex add_lock; /* Lock and mutex for SPI bus locking */ spinlock_t bus_lock_spinlock; struct mutex bus_lock_mutex; /* Flag indicating that the SPI bus is locked for exclusive use */ bool bus_lock_flag; /* * Setup mode and clock, etc (SPI driver may call many times). * * IMPORTANT: this may be called when transfers to another * device are active. DO NOT UPDATE SHARED REGISTERS in ways * which could break those transfers. */ int (*setup)(struct spi_device *spi); /* * set_cs_timing() method is for SPI controllers that supports * configuring CS timing. * * This hook allows SPI client drivers to request SPI controllers * to configure specific CS timing through spi_set_cs_timing() after * spi_setup(). */ int (*set_cs_timing)(struct spi_device *spi); /* * Bidirectional bulk transfers * * + The transfer() method may not sleep; its main role is * just to add the message to the queue. * + For now theres no remove-from-queue operation, or * any other request management * + To a given spi_device, message queueing is pure FIFO * * + The controllers main job is to process its message queue, * selecting a chip (for masters), then transferring data * + If there are multiple spi_device children, the i/o queue * arbitration algorithm is unspecified (round robin, FIFO, * priority, reservations, preemption, etc) * * + Chipselect stays active during the entire message * (unless modified by spi_transfer.cs_change != 0). * + The message transfers use clock and SPI mode parameters * previously established by setup() for this device */ int (*transfer)(struct spi_device *spi, struct spi_message *mesg); /* Called on release() to free memory provided by spi_controller */ void (*cleanup)(struct spi_device *spi); /* * Used to enable core support for DMA handling, if can_dma() * exists and returns true then the transfer will be mapped * prior to transfer_one() being called. The driver should * not modify or store xfer and dma_tx and dma_rx must be set * while the device is prepared. */ bool (*can_dma)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *xfer); struct device *dma_map_dev; struct device *cur_rx_dma_dev; struct device *cur_tx_dma_dev; /* * These hooks are for drivers that want to use the generic * controller transfer queueing mechanism. If these are used, the * transfer() function above must NOT be specified by the driver. * Over time we expect SPI drivers to be phased over to this API. */ bool queued; struct kthread_worker *kworker; struct kthread_work pump_messages; spinlock_t queue_lock; struct list_head queue; struct spi_message *cur_msg; struct completion cur_msg_completion; bool cur_msg_incomplete; bool cur_msg_need_completion; bool busy; bool running; bool rt; bool auto_runtime_pm; bool cur_msg_mapped; bool fallback; bool last_cs_mode_high; s8 last_cs[SPI_CS_CNT_MAX]; u32 last_cs_index_mask : SPI_CS_CNT_MAX; struct completion xfer_completion; size_t max_dma_len; int (*optimize_message)(struct spi_message *msg); int (*unoptimize_message)(struct spi_message *msg); int (*prepare_transfer_hardware)(struct spi_controller *ctlr); int (*transfer_one_message)(struct spi_controller *ctlr, struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_controller *ctlr); int (*prepare_message)(struct spi_controller *ctlr, struct spi_message *message); int (*unprepare_message)(struct spi_controller *ctlr, struct spi_message *message); union { int (*slave_abort)(struct spi_controller *ctlr); int (*target_abort)(struct spi_controller *ctlr); }; /* * These hooks are for drivers that use a generic implementation * of transfer_one_message() provided by the core. */ void (*set_cs)(struct spi_device *spi, bool enable); int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer); void (*handle_err)(struct spi_controller *ctlr, struct spi_message *message); /* Optimized handlers for SPI memory-like operations. */ const struct spi_controller_mem_ops *mem_ops; const struct spi_controller_mem_caps *mem_caps; /* GPIO chip select */ struct gpio_desc **cs_gpiods; bool use_gpio_descriptors; s8 unused_native_cs; s8 max_native_cs; /* Statistics */ struct spi_statistics __percpu *pcpu_statistics; /* DMA channels for use with core dmaengine helpers */ struct dma_chan *dma_tx; struct dma_chan *dma_rx; /* Dummy data for full duplex devices */ void *dummy_rx; void *dummy_tx; int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs); /* * Driver sets this field to indicate it is able to snapshot SPI * transfers (needed e.g. for reading the time of POSIX clocks) */ bool ptp_sts_supported; /* Interrupt enable state during PTP system timestamping */ unsigned long irq_flags; /* Flag for enabling opportunistic skipping of the queue in spi_sync */ bool queue_empty; bool must_async; };

spi.c里实现了transfer_one_message和transfer接口

一般SoC厂商会实现:

spi_driver结构体

注释里写着:主机端的协议驱动

struct spi_driver - Host side "protocol" driver`

id_table,就是一个识别而已。里面是name和一个私有64位数据。可以分别通过 spi.c的spi_get_device_id和spi_get_device_match_data函数获取 probe/remove作用是将driver绑定/解绑定到 spi device device_driver的作用有点大,这里的SPI作为其他驱动的底层支持,例如我们有基于SPI的RTC、PHY等等器件,那么就会用到这个。
c
/** * struct spi_driver - Host side "protocol" driver * @id_table: List of SPI devices supported by this driver * @probe: Binds this driver to the spi device. Drivers can verify * that the device is actually present, and may need to configure * characteristics (such as bits_per_word) which werent needed for * the initial configuration done during system setup. * @remove: Unbinds this driver from the spi device * @shutdown: Standard shutdown callback used during system state * transitions such as powerdown/halt and kexec * @driver: SPI device drivers should initialize the name and owner * field of this structure. * * This represents the kind of device driver that uses SPI messages to * interact with the hardware at the other end of a SPI link. Its called * a "protocol" driver because it works through messages rather than talking * directly to SPI hardware (which is what the underlying SPI controller * driver does to pass those messages). These protocols are defined in the * specification for the device(s) supported by the driver. * * As a rule, those device protocols represent the lowest level interface * supported by a driver, and it will support upper level interfaces too. * Examples of such upper levels include frameworks like MTD, networking, * MMC, RTC, filesystem character device nodes, and hardware monitoring. */ 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; };
c
/* spi */ //in /include/linux/mod_devicetable.h #define SPI_NAME_SIZE 32 #define SPI_MODULE_PREFIX "spi:" struct spi_device_id { char name[SPI_NAME_SIZE]; kernel_ulong_t driver_data; /* Data private to the driver */ }; spi_device结构体(从机设备实例)

位于 include/linux/spi

注释很清楚了,就不解释了
c
/** * struct spi_device - Controller side proxy for an SPI slave device * @dev: Driver model representation of the device. * @controller: SPI controller used with the device. * @max_speed_hz: Maximum clock rate to be used with this chip * (on this board); may be changed by the devices driver. * The spi_transfer.speed_hz can override this for each transfer. * @chip_select: Array of physical chipselect, spi->chipselect[i] gives * the corresponding physical CS for logical CS i. * @mode: The spi mode defines how data is clocked out and in. * This may be changed by the devices driver. * The "active low" default for chipselect mode can be overridden * (by specifying SPI_CS_HIGH) as can the "MSB first" default for * each word in a transfer (by specifying SPI_LSB_FIRST). * @bits_per_word: Data transfers involve one or more words; word sizes * like eight or 12 bits are common. In-memory wordsizes are * powers of two bytes (e.g. 20 bit samples use 32 bits). * This may be changed by the devices driver, or left at the * default (0) indicating protocol words are eight bit bytes. * The spi_transfer.bits_per_word can override this for each transfer. * @rt: Make the pump thread real time priority. * @irq: Negative, or the number passed to request_irq() to receive * interrupts from this device. * @controller_state: Controllers runtime state * @controller_data: Board-specific definitions for controller, such as * FIFO initialization parameters; from board_info.controller_data * @modalias: Name of the driver to use with this device, or an alias * for that name. This appears in the sysfs "modalias" attribute * for driver coldplugging, and in uevents used for hotplugging * @driver_override: If the name of a driver is written to this attribute, then * the device will bind to the named driver and only the named driver. * Do not set directly, because core frees it; use driver_set_override() to * set or clear it. * @cs_gpiod: Array of GPIO descriptors of the corresponding chipselect lines * (optional, NULL when not using a GPIO line) * @word_delay: delay to be inserted between consecutive * words of a transfer * @cs_setup: delay to be introduced by the controller after CS is asserted * @cs_hold: delay to be introduced by the controller before CS is deasserted * @cs_inactive: delay to be introduced by the controller after CS is * deasserted. If @cs_change_delay is used from @spi_transfer, then the * two delays will be added up. * @pcpu_statistics: statistics for the spi_device * @cs_index_mask: Bit mask of the active chipselect(s) in the chipselect array * * A @spi_device is used to interchange data between an SPI slave * (usually a discrete chip) and CPU memory. * * In @dev, the platform_data is used to hold information about this * device thats meaningful to the devices protocol driver, but not * to its controller. One example might be an identifier for a chip * variant with slightly different functionality; another might be * information about how this particular board wires the chips pins. */ struct spi_device { struct device dev; struct spi_controller *controller; u32 max_speed_hz; u8 chip_select[SPI_CS_CNT_MAX]; u8 bits_per_word; bool rt; #define SPI_NO_TX BIT(31) /* No transmit wire */ #define SPI_NO_RX BIT(30) /* No receive wire */ /* * TPM specification defines flow control over SPI. Client device * can insert a wait state on MISO when address is transmitted by * controller on MOSI. Detecting the wait state in software is only * possible for full duplex controllers. For controllers that support * only half-duplex, the wait state detection needs to be implemented * in hardware. TPM devices would set this flag when hardware flow * control is expected from SPI controller. */ #define SPI_TPM_HW_FLOW BIT(29) /* TPM HW flow control */ /* * All bits defined above should be covered by SPI_MODE_KERNEL_MASK. * The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart, * which is defined in include/uapi/linux/spi/spi.h. * The bits defined here are from bit 31 downwards, while in * SPI_MODE_USER_MASK are from 0 upwards. * These bits must not overlap. A static assert check should make sure of that. * If adding extra bits, make sure to decrease the bit index below as well. */ #define SPI_MODE_KERNEL_MASK (~(BIT(29) - 1)) u32 mode; int irq; void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; const char *driver_override; struct gpio_desc *cs_gpiod[SPI_CS_CNT_MAX]; /* Chip select gpio desc */ struct spi_delay word_delay; /* Inter-word delay */ /* CS delays */ struct spi_delay cs_setup; struct spi_delay cs_hold; struct spi_delay cs_inactive; /* The statistics */ struct spi_statistics __percpu *pcpu_statistics; /* Bit mask of the chipselect(s) that the driver need to use from * the chipselect array.When the controller is capable to handle * multiple chip selects & memories are connected in parallel * then more than one bit need to be set in cs_index_mask. */ u32 cs_index_mask : SPI_CS_CNT_MAX; /* * Likely need more hooks for more protocol options affecting how * the controller talks to each chip, like: * - memory packing (12 bit samples into low bits, others zeroed) * - priority * - chipselect delays * - ... */ };

spi.c(SPI总线驱动)

路径 drivers/spi/spi.c

虽然代码还不错,但是一个源码文件5000行不是梦...呕~,如果没有搜索功能,找个probe都要找半天

首先是初始化,postcore_initcall(spi_init); 该函数在初始化时就被调用。

初始化,可以查看 include/linux/init.h和段定义include/asm-generic/vmlinux.lds.h,数字越小优先级越高。postcore_initcall排第四位,优先级非常高。

spi_init的主要操作:分配内存<=32b,总线注册, class类注册,可选功能:CONFIG_SPI_SLAVE从机注册,CONFIG_OF_DYNAMIC设备树,CONFIG_ACPI注册即ACPI表

注册用到的结构体:

c
const struct bus_type spi_bus_type = { .name = "spi", .dev_groups = spi_dev_groups, .match = spi_match_device, .uevent = spi_uevent, .probe = spi_probe, .remove = spi_remove, .shutdown = spi_shutdown, }; EXPORT_SYMBOL_GPL(spi_bus_type);

spi_dev_groups 是sysfs组,里面一车面包人(很多sysfs读写接口,从56行到311行都是: drivers/spi/spi.c#L56-L311

spi_match_device 很简单,就是提供匹配函数,spidev.c后面会用到。其实就是override-设备树-ACPI-id_table这样优先级匹配而已。

spi_uevent 是uevent事件回调,uevent提供了“用户空间通知”的功能实现,当内核中有Kobject的增加/删除/修改等动作时,会通知用户空间

spi_shutdown 是关闭回调,这里只是调用dev->driver->shutdown函数而已。

spi_probe/spi_remove

其中spi_probe是这样的:

从设备树节点获取并配置:时钟、irq中断、 dev_pm_domain_attach 附加到电源管理域(power manager domain)上 调用struct spi_device->struct device dev->struct device_driver driver的probe函数

可选功能注册的结构体是

c
static struct class spi_master_class = { .name = "spi_master", .dev_release = spi_controller_release, .dev_groups = spi_master_groups, }; //... static struct class spi_slave_class = { .name = "spi_slave", .dev_release = spi_controller_release, .dev_groups = spi_slave_groups, }; spi_controller_release 就一个kfree释放内存 spi_master_groups和spi_slave_groups就是一车面包人(sysfs组

其他的就都通过EXPORT_SYMBOL_GPL宏向外暴露函数API了,多达40个...

SPI数据传输-内核态API

暴露API:EXPORT_SYMBOL_GPL(spi_sync);

流程:spi_sync==互斥锁=>__spi_sync

作用:将spi_message
c
static int __spi_sync(struct spi_device *spi, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); unsigned long flags; int status; struct spi_controller *ctlr = spi->controller; if (__spi_check_suspended(ctlr)) { dev_warn_once(&spi->dev, "Attempted to sync while suspend\n"); return -ESHUTDOWN; } status = spi_maybe_optimize_message(spi, message); if (status) return status; SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync); SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_sync); /* * Checking queue_empty here only guarantees async/sync message * ordering when coming from the same context. It does not need to * guard against reentrancy from a different context. The io_mutex * will catch those cases. */ if (READ_ONCE(ctlr->queue_empty) && !ctlr->must_async) { message->actual_length = 0; message->status = -EINPROGRESS; trace_spi_message_submit(message); SPI_STATISTICS_INCREMENT_FIELD(ctlr->pcpu_statistics, spi_sync_immediate); SPI_STATISTICS_INCREMENT_FIELD(spi->pcpu_statistics, spi_sync_immediate); __spi_transfer_message_noqueue(ctlr, message); return message->status; } /* * There are messages in the async queue that could have originated * from the same context, so we need to preserve ordering. * Therefor we send the message to the async queue and wait until they * are completed. */ message->complete = spi_complete; message->context = &done; spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags); status = __spi_async(spi, message); spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags); if (status == 0) { wait_for_completion(&done); status = message->status; } message->context = NULL; return status; }

另外还有一个异步API spi_async,里面用了队列,队列空则使用同步,队列不空则调用__spi_async将内容通过spi_controller->transfer(spi, message)发送。

spi-xxx.c 片上SPI控制器驱动-原厂

如赛灵思的SOC会使用drivers/spi/spi-xilinx.c#platform_driver

的probe来初始化spi_controller结构体以及SoC片上SPI控制器。如下图440行就是SPI控制器采用bitbang方式收发函数的指针赋值。

spidev.c (提供用户态支持的SPIDEV内核驱动)

路径 drivers/spi/spidev.c

它是SPI userspace API的内核驱动支持。

入口函数,注册了 字符设备、Class、spi_driver

注册字符设备 chrdev
c
873 status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);

其字符设备的fops如下

c
static const struct file_operations spidev_fops = { .owner = THIS_MODULE, /* REVISIT switch to aio primitives, so that userspace * gets more complete API coverage. Itll simplify things * too, except for the locking. */ .write = spidev_write, .read = spidev_read, .unlocked_ioctl = spidev_ioctl, .compat_ioctl = spidev_compat_ioctl, .open = spidev_open, .release = spidev_release, .llseek = no_llseek, };

定义了 open/close/read/write/64位ioctl/32位ioctl/llseek 这几个常见接口。

open/close写法类似,这里只截图 spidev_open

read/write写法类似,这里只截图了spidev_write函数内容

这里将用户态数据复制到dev的私有数据spidev_data里。然后调用spidev_sync_write将私有数据写入硬件。

spidev_sync_write队列>spidev_sync互斥锁>spidev_sync_unlocked===>位于spi.c的内核态驱动spi_sync
c
struct spidev_data { dev_t devt; struct mutex spi_lock; struct spi_device *spi; struct list_head device_entry; /* TX/RX buffers are NULL unless this device is open (users > 0) */ struct mutex buf_lock; unsigned users; u8 *tx_buffer; u8 *rx_buffer; u32 speed_hz; };

ioctl里面主要是分支调用 spi_setup 配置参数

其中调用32位ioctl时,SPI_IOC_MAGIC _IOC_NR _IOC_WRITE 这三种会走32位兼容性操作,其他都会转给64位ioctl处理。

llseek 指向 no_llseek,即没有!

注册Class name="spidev"
c
877 status = class_register(&spidev_class);

其Class的name成员值为"spidev"

c
/* The main reason to have this class is to make mdev/udev create the * /dev/spidevB.C character device nodes exposing our userspace API. * It also simplifies memory management. */ static const struct class spidev_class = { .name = "spidev", }; 注册driver
c
883 status = spi_register_driver(&spidev_spi_driver);
c
static struct spi_driver spidev_spi_driver = { .driver = { .name = "spidev", .of_match_table = spidev_dt_ids, .acpi_match_table = spidev_acpi_ids, }, .probe = spidev_probe, .remove = spidev_remove, .id_table = spidev_spi_ids, /* NOTE: suspend/resume methods are not necessary here. * We dont do anything except pass the requests to/from * the underlying controller. The refrigerator handles * most issues; the controller driver handles the rest. */ };

其中

name="spidev" dts/acpi匹配

id_table

probe/remove

这里的probe内容稍微多点
c
static int spidev_probe(struct spi_device *spi) { int (*match)(struct device *dev); struct spidev_data *spidev; int status; unsigned long minor; match = device_get_match_data(&spi->dev); if (match) { status = match(&spi->dev); /*...*/ } /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); if (!spidev) return -ENOMEM; /* Initialize the driver data */ spidev->spi = spi; mutex_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->controller->bus_num, spi_get_chipselect(spi, 0)); status = PTR_ERR_OR_ZERO(dev); } else {/*...*/} 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); /*...*/ return status; }

为节省篇幅,容错处理我用/*...*/代替了。

这里probe主要就是 获取spi总线bus_type结构体的.match成员函数指针,然后调用match函数。(override-设备树-ACPI-id_tables这样的优先级顺序) 给结构体分配内存 填充结构体 创建字符设备 配置driver的私有数据成员

应用层

内核给应用层准备了一个 SPI userspace API,底层就是spidev.c,只需要在内核menuconfig配置开关,然后open /dev/spidevB.C 即可。如果需要全双工,可以看看 tools/spi/spidev_fdx.c 这个例子。

另外有个很重要的事情要记得,ioctl使用的结构体,它的tx_buf/rx_buf只有64位长,而不像有些RTOS那样是个指针,所以Linux内核把形式参数做成了接收spi_ioc_transfer数组。

c
struct spi_ioc_transfer { __u64 tx_buf; __u64 rx_buf; __u32 len; __u32 speed_hz; __u16 delay_usecs; __u8 bits_per_word; __u8 cs_change; __u8 tx_nbits; __u8 rx_nbits; __u8 word_delay_usecs; __u8 pad; /* If the contents of struct spi_ioc_transfer ever change * incompatibly, then the ioctl number (currently 0) must change; * ioctls with constant size fields get a bit more in the way of * error checking than ones (like this) where that field varies. * * NOTE: struct layout is the same in 64bit and 32bit userspace. */ };

总结:

总线驱动 spi.c: 在内核postcore_init阶段注册bus_type{probe\match\sysfs\uevent等},这里的probe就会最后调用到SoC片上SPI控制器驱动的probe 控制器驱动 spi_controller 由各SoC厂商提供 spi-xxx.c 的 platform_driver初始化片上的SPI控制器。 主机驱动 spi_driver 从机驱动(板载外设,自己写) 内核态 spi_device 用户态 spidev

看得很折磨...

__EOF__

本文作者: YuCloud 本文链接: https://www.cnblogs.com/yucloud/p/18162832/Linux_kernel_driver_SPI 关于博主: love to create 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处! 声援博主: Click【推荐】if the article helpful, and Welcome to discuss

群贤毕至

访客