Linux内核之SPI协议
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
看得很折磨...