spi.c是spi子系统初始化的核心代码,由内核负责初始化
spidev.c是spi用户接口初始化的代码,编译的时候需要选择该模块
spi_sam.c是平台驱动的初始化代码,编译时需要选择spi s3c64xx模块
drivers/spi/spi_test.c 这个文件可以细看,是详细分析spi驱动的好文件。
spi.c也就是spi子系统的核心了,
spi_sam.c是s3c64xx系列芯片的SPI controller驱动,它向更上层的SPI核心层(spi.c)提供接口用来控制芯片的SPI controller,是一个被其他驱动使用的驱动。
spidev.c是在核心层基础之上将SPI controller模拟成一个字符型的驱动,向文件系统提供标准的文件系统接口,用来操作对应的SPI controller
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
module_init(spidev_init);
/*-------------------------------------------------------------------------*/
static int __init spidev_init(void)
{
int status;
/* Claim our 256 reserved device numbers. Then register a class
* that will key udev/mdev to add/remove /dev nodes. Last, register
* the driver which manages those device numbers.
*/
BUILD_BUG_ON(N_SPI_MINORS > 256);
//字符设备注册,将SPI注册为字符设备,绑定操作函数集合spidev_fops
//注册字符设备,参数spidev_fops是struct file_operations的实例,这里就可以知道,用户程序的open、//write等操作最终会调用这里面的函数
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
if (status < 0)
return status;
spidev_class = class_create(THIS_MODULE, "spidev");
if (IS_ERR(spidev_class)) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
return PTR_ERR(spidev_class);
}
/*注册spidev的driver,可与modalias字段为"spidev"的spi_device匹配*/
status = spi_register_driver(&spidev_spi);
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
}
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static 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,
.open = spidev_open,
.release = spidev_release,
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
第12章 SPI只是一种总线,spi_driver的作用只是将SPI外设挂接在总线上,因此在spi_driver的probe()成员函数中,将注册SPI外设本身所属设备驱动的类型
static struct spi_driver spidev_spi = {
/*spidev的driver,可与modalias字段为"spidev"的spi_device匹配*/
//
//static struct spi_board_info __initdata sam_spi_devs[] = {
// [0] = { .modalias = "spidev", /* Test Interface */
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe,
.remove = __devexit_p(spidev_remove),
/* 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.
*/
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
spi子系统初始化的核心代码在spi.c里
/**
* spi_register_driver - register a SPI driver
* @sdrv: the driver to register
* Context: can sleep
*/
int spi_register_driver(struct spi_driver *sdrv)
{
//spi注册驱动的.bus注意看看
sdrv->driver.bus = &spi_bus_type;
if (sdrv->probe)
sdrv->driver.probe = spi_drv_probe;
if (sdrv->remove)
sdrv->driver.remove = spi_drv_remove;
if (sdrv->shutdown)
sdrv->driver.shutdown = spi_drv_shutdown;
return driver_register(&sdrv->driver);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
spi子系统初始化的核心代码在spi.c里
spi_bus_type对应SPI中的SPI BUS总线
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
//驱动和设备匹配时调用.match函数进行匹配
//匹配成功后调用驱动的.probe函数
.match = spi_match_device,
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
spi子系统初始化的核心代码在spi.c里
/* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
* and the sysfs version makes coldplug work too.
*/
//匹配函数
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
return strncmp(spi->modalias, drv->name, BUS_ID_SIZE) == 0;
/*spidev的driver,可与modalias字段为"spidev"的spi_device匹配*/
//
//static struct spi_board_info __initdata sam_spi_devs[] = {
// [0] = { .modalias = "spidev", /* Test Interface */
//
//static struct spi_driver spidev_spi = { .driver = { .name = "spidev",
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* INTERFACES between SPI master-side drivers and SPI infrastructure.
* (There's no SPI slave support for Linux yet...)
*/
extern struct bus_type spi_bus_type;
/**
* struct spi_device - Master side proxy for an SPI slave device
* @dev: Driver model representation of the device.
* @master: 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: Chipselect, distinguishing chips handled by @master.
* @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.
* @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
*
* 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.
*/
//用spi_device来描述一个SPI外设设备
struct spi_device {
struct device dev;
// .modalias = "spidev"的一对(设备和驱动)module_init(spidev_init)
//通过内部包含struct spi_master *master;成员
//与.name = "sam-spi",的一对(平台设备和驱动)module_init(sam_spi_init);联系起来,
//平台设备驱动通过samspi_probe 函数------>spi_alloc_master产生并赋值struct spi_master实例
//见static inline int spi_async(struct spi_device *spi, struct spi_message *message)
struct spi_master *master;//对应的控制器指针
u32 max_speed_hz; //spi通信的时钟
u8 chip_select; //片选,用于区分同一总线上的不同设备
u8 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_SLAVE 0x40 /* SLAVE mode if this bit is set */
#define SPIDEV_MAX_BUFFSIZE 16384
u8 bits_per_word; //每个字长的比特数
int irq; //使用的中断
void *controller_state;
void *controller_data;
char modalias[32]; //名字 ,用来匹配
/*
* 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
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};
///////////////////////////////
/**
* struct spi_driver - Host side "protocol" 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
* @suspend: Standard suspend callback used during system state transitions
* @resume: Standard resume callback used during system state transitions
* @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.
*/
//用spi_driver来描述一个SPI外设驱动
struct spi_driver {
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
//spi_device与spi_master是同一个父设备,这是在spi_new_device()-->spi_alloc_device函数中设定的,
//一般这个设备是一个物理设备
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*-------------------------------------------------------------------------*/
/* Read-only message with current device setup */
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct spidev_data *spidev;
ssize_t status = 0;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidev = filp->private_data;
mutex_lock(&spidev->buf_lock);
status = spidev_sync_read(spidev, count);
if (status > 0) {
unsigned long missing;
missing = copy_to_user(buf, spidev->buffer, status);
if (missing == status)
status = -EFAULT;
else
status = status - missing;
}
mutex_unlock(&spidev->buf_lock);
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
int retval = 0;
struct spidev_data *spidev;
struct spi_device *spi;
u32 tmp;
unsigned n_ioc;
struct spi_ioc_transfer *ioc;
/* Check type and command number */
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
return -ENOTTY;
/* Check access direction once here; don't repeat below.
* IOC_DIR is from the user perspective, while access_ok is
* from the kernel perspective; so they look reversed.
*/
//检查地址是否合法
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,
(void __user *)arg, _IOC_SIZE(cmd));
if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,
(void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
/* guard against device removal before, or while,
* we issue this ioctl.
*/
spidev = filp->private_data;
spin_lock_irq(&spidev->spi_lock);
spi = spi_dev_get(spidev->spi);
spin_unlock_irq(&spidev->spi_lock);
if (spi == NULL)
return -ESHUTDOWN;
/* use the buffer lock here for triple duty:
* - prevent I/O (from us) so calling spi_setup() is safe;
* - prevent concurrent SPI_IOC_WR_* from morphing
* data fields while SPI_IOC_RD_* reads them;
* - SPI_IOC_MESSAGE needs the buffer locked "normally".
*/
mutex_lock(&spidev->buf_lock);
switch (cmd) {
/* read requests */
//设置SPI读模式
case SPI_IOC_RD_MODE:
retval = __put_user(spi->mode & SPI_MODE_MASK,
(__u8 __user *)arg);
break;
case SPI_IOC_RD_LSB_FIRST:
retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
(__u8 __user *)arg);
break;
case SPI_IOC_RD_BITS_PER_WORD:
retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
break;
case SPI_IOC_RD_MAX_SPEED_HZ:
retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
break;
/* write requests */
//设置SPI成写模式
case SPI_IOC_WR_MODE:
retval = __get_user(tmp, (u8 __user *)arg);
if (retval == 0) {
//先将SPI之前的模式保存起来,一旦设置失败,进行回复
u8 save = spi->mode;
if (tmp & ~SPI_MODE_MASK) {
retval = -EINVAL;
break;
}
tmp |= spi->mode & ~SPI_MODE_MASK;
spi->mode = (u8)tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
}
break;
case SPI_IOC_WR_LSB_FIRST:
retval = __get_user(tmp, (__u8 __user *)arg);
if (retval == 0) {
u8 save = spi->mode;
if (tmp)
spi->mode |= SPI_LSB_FIRST;
else
spi->mode &= ~SPI_LSB_FIRST;
retval = spi_setup(spi);
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "%csb first\n",
tmp ? 'l' : 'm');
}
break;
case SPI_IOC_WR_BITS_PER_WORD:
retval = __get_user(tmp, (__u8 __user *)arg);
if (retval == 0) {
u8 save = spi->bits_per_word;
spi->bits_per_word = tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->bits_per_word = save;
else
dev_dbg(&spi->dev, "%d bits per word\n", tmp);
}
break;
case SPI_IOC_WR_MAX_SPEED_HZ:
retval = __get_user(tmp, (__u32 __user *)arg);
if (retval == 0) {
u32 save = spi->max_speed_hz;
spi->max_speed_hz = tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->max_speed_hz = save;
else
dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
}
break;
default:
/* segmented and/or full-duplex I/O request */
if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|| _IOC_DIR(cmd) != _IOC_WRITE) {
retval = -ENOTTY;
break;
}
//得到用户空间数据的大小
tmp = _IOC_SIZE(cmd);
//如果这些数据不能分成spi_ioc_transfer的整数倍,则不能进行传输
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {
retval = -EINVAL;
break;
}
//能分出的个数
n_ioc = tmp / sizeof(struct spi_ioc_transfer);
if (n_ioc == 0)
break;
//申请内存
/* copy into scratch area */
ioc = kmalloc(tmp, GFP_KERNEL);
if (!ioc) {
retval = -ENOMEM;
break;
}
//将用户空间的数据拷贝到内核空间来
if (__copy_from_user(ioc, (void __user *)arg, tmp)) {
kfree(ioc);
retval = -EFAULT;
break;
}
//进行数据传输
/* translate to spi_message, execute */
retval = spidev_message(spidev, ioc, n_ioc);
kfree(ioc);
break;
}
mutex_unlock(&spidev->buf_lock);
spi_dev_put(spi);
return retval;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static int spidev_open(struct inode *inode, struct file *filp)
{
struct spidev_data *spidev;
int status = -ENXIO;
lock_kernel();
mutex_lock(&device_list_lock);
// static LIST_HEAD(device_list);
//通过链表头device_list和节点 device_entry获取到包含成员device_entry的 struct spidev_data *spidev;,而且是遍历链表
list_for_each_entry(spidev, &device_list, device_entry) {
if (spidev->devt == inode->i_rdev) {
//遍历链表,找到了和这个文件节点绑定的设备相同的在链表里的那个设备了
status = 0;
break;
}
}
if (status == 0) {
if (!spidev->buffer) {
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
}
}
if (status == 0) {
spidev->users++;
//spidev_read是从filp->private_data里取出spidev
filp->private_data = spidev;
nonseekable_open(inode, filp);
}
} else
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
mutex_unlock(&device_list_lock);
unlock_kernel();
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//-书中说probe是用来查询特定设备是否存在的函数
//驱动和设备匹配成功后会调用spidev_probe
//这个函数实现把spidev添加到device_list
static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
/* Allocate driver data */
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
if (!spidev)
return -ENOMEM;
/* Initialize the driver data */
spidev->spi = spi;
spin_lock_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->master->bus_num, spi->chip_select);
status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
} else {
dev_dbg(&spi->dev, "no minor number available!\n");
status = -ENODEV;
}
if (status == 0) {
set_bit(minor, minors);
//spidev->device_entry这个链表节点插入了device_list中,这样spidev就可以通过device_list而获取到了
list_add(&spidev->device_entry, &device_list);
}
mutex_unlock(&device_list_lock);
if (status == 0)
spi_set_drvdata(spi, spidev);
else
kfree(spidev);
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static int spidev_message(struct spidev_data *spidev,
struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
struct spi_message msg;
struct spi_transfer *k_xfers;
struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp;
unsigned n, total;
u8 *buf;
int status = -EFAULT;
//初始化spi_message的链表头
spi_message_init(&msg);
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
if (k_xfers == NULL)
return -ENOMEM;
/* Construct spi_message, copying any tx data to bounce buffer.
* We walk the array of user-provided transfers, using each one
* to initialize a kernel version of the same transfer.
*/
buf = spidev->buffer;
total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n;
n--, k_tmp++, u_tmp++) {
k_tmp->len = u_tmp->len;
total += k_tmp->len;
if (total > bufsiz) {
status = -EMSGSIZE;
goto done;
}
if (u_tmp->rx_buf) {
k_tmp->rx_buf = buf;
if (!access_ok(VERIFY_WRITE, (u8 __user *)
(uintptr_t) u_tmp->rx_buf,
u_tmp->len))
goto done;
}
if (u_tmp->tx_buf) {
k_tmp->tx_buf = buf;
if (copy_from_user(buf, (const u8 __user *)
(uintptr_t) u_tmp->tx_buf,
u_tmp->len))
goto done;
}
buf += k_tmp->len;
k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->bits_per_word = u_tmp->bits_per_word;
k_tmp->delay_usecs = u_tmp->delay_usecs;
k_tmp->speed_hz = u_tmp->speed_hz;
#ifdef VERBOSE
dev_dbg(&spi->dev,
" xfer len %zd %s%s%s%dbits %u usec %uHz\n",
u_tmp->len,
u_tmp->rx_buf ? "rx " : "",
u_tmp->tx_buf ? "tx " : "",
u_tmp->cs_change ? "cs " : "",
u_tmp->bits_per_word ? : spi->bits_per_word,
u_tmp->delay_usecs,
u_tmp->speed_hz ? : spi->max_speed_hz);
#endif
//将spi_transfer类型的实体k_tmp通过transfer_list字段挂到spi_message的transfer队列上
spi_message_add_tail(k_tmp, &msg);
}
//调用底层的传输函数
status = spidev_sync(spidev, &msg);
if (status < 0)
goto done;
/* copy any rx data out of bounce buffer */
buf = spidev->buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) {
if (__copy_to_user((u8 __user *)
(uintptr_t) u_tmp->rx_buf, buf,
u_tmp->len)) {
status = -EFAULT;
goto done;
}
}
buf += u_tmp->len;
}
status = total;
done:
kfree(k_xfers);
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
//声明并初始化一个完成量
DECLARE_COMPLETION_ONSTACK(done);
int status;
//指定spi_message结构实例的完成量complete函数指针赋值
//handle_msg函数有一行 if(msg->complete) msg->complete(msg->context);
//这行调用的便是spidev_complete这个函数
message->complete = spidev_complete;
message->context = &done;
spin_lock_irq(&spidev->spi_lock);
if (spidev->spi == NULL)
status = -ESHUTDOWN;
else
//调用spi核心中的函数进行数据传输
status = spi_async(spidev->spi, message);
spin_unlock_irq(&spidev->spi_lock);
if (status == 0) {
//等待完成量被唤醒
//handle_msg函数有一行 if(msg->complete) msg->complete(msg->context);
//这行调用的便是spidev_complete这个函数 ,当msg->complete(msg->context);调用后此处被唤醒往下执行
wait_for_completion(&done);
status = message->status;
if (status == 0)
status = message->actual_length;
}
return status;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
static inline int
spi_async(struct spi_device *spi, struct spi_message *message)
{
message->spi = spi;
//看看.name = "spidev"的这对设备和驱动
//在这里与.name = "sam-spi",的一对平台设备和驱动联系起来
//module_init(spidev_init);与
// module_init(sam_spi_init);这两个模块的连接点在这行程序里体现
//struct spi_device里的master成员是什么时候赋值过去的?
//samspi_probe(struct platform_device *pdev)------>spi_register_master(master);
//---------->scan_boardinfo(master);------->spi_new_device(master, chip);--------->proxy = spi_alloc_device(master);
///在spi_alloc_device函数里有一行spi->master = master;,返回值 return spi;
/////////这个便是调用samspi_transfer函数了。数据的发送传输
return spi->master->transfer(spi, message);
}
阅读(1842) | 评论(0) | 转发(0) |