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 控制器驅動進行初始化。
/**
* 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 it's 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 it's 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
* device's SPI controller; protocol code may call this. This
* must fail if an unrecognized or unsupported mode is requested.
* It's 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 controller's 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
* message's 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 board's 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) /* Can't do full duplex */
#define SPI_CONTROLLER_NO_RX BIT(1) /* Can't do buffer read */
#define SPI_CONTROLLER_NO_TX BIT(2) /* Can't 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 there's no remove-from-queue operation, or
* any other request management
* + To a given spi_device, message queueing is pure FIFO
*
* + The controller's 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等等器件,那麼就會用到這個。
/**
* 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 weren't 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. It's 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;
};
/* 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
註釋很清楚了,就不解釋了
/**
* 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 device's 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 device's 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 device's 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: Controller's 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 that's meaningful to the device's 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 chip's 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總線驅動)
雖然代碼還不錯,但是一個源碼文件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表
註冊用到的結構體:
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函數
可選功能註冊的結構體是
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
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內核驅動)
它是SPI userspace API的內核驅動支持。
入口函數,註冊了 字符設備、Class、spi_driver
註冊字符設備 chrdev
873 status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
其字符設備的fops如下
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll 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
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"
877 status = class_register(&spidev_class);
其Class的name成員值爲"spidev"
/* 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
883 status = spi_register_driver(&spidev_spi_driver);
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 don't 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內容稍微多點
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數組。
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
看得很折磨...