SPI是”Serial Peripheral Interface” 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器、传感器、存储设备,SPI设备分为主设备和从设备两种,用于通信和控制的四根线分别是:
本文是基于linux-3.0.35内核与imx6q硬件
imx6q 支持外挂四个SPI从设备,ECSPI的SPI0支持salve mode,内核没有实现,所以要用这种模式,要自己写驱动才行。
在这里用的从设备是sc16is752, spi转uart的一颗芯片.
按照时钟信号和数据信号之间的相位关系,SPI有4种工作时序模式:
我们用CPOL表示时钟信号的初始电平的状态,CPOL为0表示时钟信号初始状态为低电平,为1表示时钟信号的初始电平是高电平。另外,我们用CPHA来表示在那个时钟沿采样数据,CPHA为0表示在首个时钟变化沿采样数据,而CPHA为1则表示要在第二个时钟变化沿来采样数据。用CPOL和CPHA的组合来表示当前SPI需要的工作模式:
sc16is752 只支持 模式0
这里我们使用linux内核自带的spidev的一个驱动,涉及的所有文件如下:
./drivers/spi/spidev.c
./drivers/spi/spidev.h
./drivers/spi/spi.c
./include/linux/spi/spi.h
./drivers/spi/spi_bitbang.c
./drivers/spi/spi_bitbang.h
./drivers/spi/spi_imx.c
./arch/arm/plat-mxc/devices/platform-spi_imx.c
./arch/arm/mach-mx6/board-mx6q_sabreauto.h
./arch/arm/mach-mx6/board-mx6q_sabreauto.c
spi的子系统是基于平台设备框架的,所以设备与驱动的名字必须要一致才能匹配成功。
imx6的配置是相当复杂的,一个引脚可以通过mux来配置成不同的功能
static iomux_v3_cfg_t mx6q_sabreauto_pads[] = {
.
.
.
//ECSPI2
MX6Q_PAD_DISP0_DAT19__ECSPI2_SCLK,
MX6Q_PAD_DISP0_DAT17__ECSPI2_MISO,
MX6Q_PAD_DISP0_DAT16__ECSPI2_MOSI,
MX6Q_PAD_DISP0_DAT18__ECSPI2_SS0,
MX6Q_PAD_DISP0_DAT22__GPIO_5_16, //这个引脚可以做为SC16IS752的中断输入(本文不用)
.
.
.
.
};
static struct spi_board_info imx6_sabresd_spi_uart[] __initdata = {
{
.modalias = "sc16is752", //这个别名非常重要,必须要和spidev.c驱动里的名字保持一致
.max_speed_hz = 1000000, //最大的速度
.bus_num = 1, //这里我们用的ECSPI2,下标要注意
.chip_select = 0, //片选,是低电平有效
.mode = SPI_MODE_0, //使用的SPI模式0
// .irq = gpio_to_irq(SPI_UART_IRQ),
},
};
static void spi_device_init(void)
{
spi_register_board_info(imx6_sabresd_spi_uart, ARRAY_SIZE(imx6_sabresd_spi_uart));
}
static void __init mx6_board_init(void)
{
。。。
/* SPI */
imx6q_add_ecspi(0, &mx6q_sabreauto_spi1_data);
imx6q_add_ecspi(1, &mx6q_sabreauto_spi2_data);
imx6q_add_ecspi(2, &mx6q_sabreauto_spi3_data);
spi_device_init();
。。。。
}
static struct spi_driver spidev_spi_driver = {
.driver = {
//.name = "spidev",
.name = "sc16is752", // 这里改为sc16is752,当然改spi_board_info 里的值也是一样的,只要一致
.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.
*/
};
内核里提供了spidev驱动的测试代码,位于
./documentation/spi目录下的spidev_test.c
可以先将MISO与MOSI两根张用镊子短接,如果看到收发的数据是一样的则驱动已经正常工作了。剩下的事就是SC16IS752的配置问题了。
若要把子系统分析明白,得从以下几个结构体入手:
spi_transfer 结构是一次spi传送,多个spi_transfer 组成了一个spi_message
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
unsigned cs_change:1;
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;-------------------------|
}; |
|
struct spi_message { |
struct list_head transfers;--------------------------|
struct spi_device *spi;
unsigned is_dma_mapped:1;
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned actual_length;
int status;
struct list_head queue;
void *state;
};
操作接口函数:
static inline void spi_message_init(struct spi_message *m)
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
static inline void spi_transfer_del(struct spi_transfer *t)
这个结构代表了一条spi总线
struct spi_master {
struct device dev; //继承自device
struct list_head list; //spi_register_master 的时候会把spi_master注册到 spi_master_list这个双向链表上
s16 bus_num; //第几根bus
u16 num_chipselect; //这个spi master有几个片选信号
u16 dma_alignment;
u16 mode_bits; //spi 协议的mode位
u16 flags;
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
bool bus_lock_flag;
// 以下的三个接口都定义在spi_imx.c文件里
//static int spi_imx_setup(struct spi_device *spi)
//static int spi_imx_transfer(struct spi_device *spi,struct spi_transfer *transfer)
//static void spi_imx_cleanup(struct spi_device *spi)
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
void (*cleanup)(struct spi_device *spi);
};
struct spi_device {
struct device dev; //继承自device
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
u8 bits_per_word; //一个word多少个bit
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE]; //这个会从spi_board_info的modalias域拷贝过来,驱动与设备匹配的时候会比较
}
//板级初始化的时候会根据spi_board_info ,new一个spi_device出来
struct spi_device *spi_new_device(struct spi_master *master,
struct spi_board_info *chip)
{
struct spi_device *proxy;
int status;
proxy = spi_alloc_device(master);
if (!proxy)
return NULL;
WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));
proxy->chip_select = chip->chip_select;//片选,是低有效还是高有效
proxy->max_speed_hz = chip->max_speed_hz; //最大频率
proxy->mode = chip->mode; //spi操作模式
proxy->irq = chip->irq;//中断号,我这里没有使用
strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));//拷贝别名
proxy->dev.platform_data = (void *) chip->platform_data; //其它的数据可以通过这个指针取到
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
status = spi_add_device(proxy);
if (status < 0) {
spi_dev_put(proxy);
return NULL;
}
return proxy;
}
spi_driver & spi_device的匹配过程:
它们如匹配的呢?—————spi_imx.c的平台设备驱动注册的时候
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);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
//平台驱动初始化:
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = __devexit_p(spi_imx_remove),
};
static int __init spi_imx_init(void)
{
return platform_driver_register(&spi_imx_driver);
}
//platform_driver_register 会调用 bus_add_driver
int bus_add_driver(struct device_driver *drv)
{
.
.
.
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv); //here
if (error)
goto out_unregister;
}
.
.
.
}
static int __driver_attach(struct device *dev, void *data)
{
.
.
.
if (!driver_match_device(drv, dev)) //在这里会进行匹配
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev); //匹配成功了之后再probe
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0; //这里最终是匹配的名字
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
.
.
.
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); //here
pm_runtime_put_sync(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
.
.
.
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev); //here
if (ret)
goto probe_failed;
}
.
.
.
}
imx spi平台驱动probe函数:
static int __devinit spi_imx_probe(struct platform_device *pdev)
{
struct spi_imx_master *mxc_platform_info;
struct spi_master *master;
struct spi_imx_data *spi_imx;
struct resource *res;
int i, ret;
mxc_platform_info = dev_get_platdata(&pdev->dev);
if (!mxc_platform_info) {
dev_err(&pdev->dev, "can't get the platform data\n");
return -EINVAL;
}
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
if (!master)
return -ENOMEM;
platform_set_drvdata(pdev, master);
master->bus_num = pdev->id;
master->num_chipselect = mxc_platform_info->num_chipselect;
spi_imx = spi_master_get_devdata(master);
spi_imx->bitbang.master = spi_master_get(master);
spi_imx->chipselect = mxc_platform_info->chipselect;
for (i = 0; i < master->num_chipselect; i++) {
if (spi_imx->chipselect[i] < 0)
continue;
ret = gpio_request(spi_imx->chipselect[i], DRIVER_NAME);
if (ret) {
while (i > 0) {
i--;
if (spi_imx->chipselect[i] >= 0)
gpio_free(spi_imx->chipselect[i]);
}
dev_err(&pdev->dev, "can't get cs gpios\n");
goto out_master_put;
}
}
//这里的函数指针初始化比较重要
spi_imx->bitbang.chipselect = spi_imx_chipselect;
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
spi_imx->bitbang.master->setup = spi_imx_setup;
spi_imx->bitbang.master->cleanup = spi_imx_cleanup;
spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
init_completion(&spi_imx->xfer_done);
spi_imx->devtype_data =
spi_imx_devtype_data[pdev->id_entry->driver_data];
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "can't get platform resource\n");
ret = -ENOMEM;
goto out_gpio_free;
}
if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
dev_err(&pdev->dev, "request_mem_region failed\n");
ret = -EBUSY;
goto out_gpio_free;
}
spi_imx->base = ioremap(res->start, resource_size(res));
if (!spi_imx->base) {
ret = -EINVAL;
goto out_release_mem;
}
spi_imx->irq = platform_get_irq(pdev, 0);
if (spi_imx->irq < 0) {
ret = -EINVAL;
goto out_iounmap;
}
/*上面我说没有用到中断,那这个中断什么鬼呢?这个中断是收发数据的SPI中断,上面说的中断是外接的从设备当数据准备好或是其它的情况的时候向IMX6输入的一个电平。*/
ret = request_irq(spi_imx->irq, spi_imx_isr, 0, DRIVER_NAME, spi_imx);
if (ret) {
dev_err(&pdev->dev, "can't get irq%d: %d\n", spi_imx->irq, ret);
goto out_iounmap;
}
spi_imx->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(spi_imx->clk)) {
dev_err(&pdev->dev, "unable to get clock\n");
ret = PTR_ERR(spi_imx->clk);
goto out_free_irq;
}
clk_enable(spi_imx->clk);
spi_imx->spi_clk = clk_get_rate(spi_imx->clk);
spi_imx->devtype_data.reset(spi_imx);
spi_imx->devtype_data.intctrl(spi_imx, 0);
ret = spi_bitbang_start(&spi_imx->bitbang);
if (ret) {
dev_err(&pdev->dev, "bitbang start failed with %d\n", ret);
goto out_clk_put;
}
clk_disable(spi_imx->clk);
//最后打印到这里
dev_info(&pdev->dev, "probed\n");
return ret;
out_clk_put:
clk_disable(spi_imx->clk);
clk_put(spi_imx->clk);
out_free_irq:
free_irq(spi_imx->irq, spi_imx);
out_iounmap:
iounmap(spi_imx->base);
out_release_mem:
release_mem_region(res->start, resource_size(res));
out_gpio_free:
for (i = 0; i < master->num_chipselect; i++)
if (spi_imx->chipselect[i] >= 0)
gpio_free(spi_imx->chipselect[i]);
out_master_put:
spi_master_put(master);
kfree(master);
platform_set_drvdata(pdev, NULL);
return ret;
}
从上到下分析一个数据怎么从应用层传到寄存器的,这里还是借用内核自己带的spidev驱动
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_init(&msg); //message 初始化
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
if (k_xfers == NULL)
return -ENOMEM;
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;
// 把transfer添加到message的链表上
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 void bitbang_work(struct work_struct *work)
{
struct spi_bitbang *bitbang =
container_of(work, struct spi_bitbang, work);
unsigned long flags;
spin_lock_irqsave(&bitbang->lock, flags);
bitbang->busy = 1;
while (!list_empty(&bitbang->queue)) { //遍历bitbang的队列
struct spi_message *m;
struct spi_device *spi;
unsigned nsecs;
struct spi_transfer *t = NULL;
unsigned tmp;
unsigned cs_change;
int status;
int do_setup = -1;
m = container_of(bitbang->queue.next, struct spi_message,
queue);
list_del_init(&m->queue);
spin_unlock_irqrestore(&bitbang->lock, flags);
/* FIXME this is made-up ... the correct value is known to
* word-at-a-time bitbang code, and presumably chipselect()
* should enforce these requirements too?
*/
nsecs = 100;
spi = m->spi;
tmp = 0;
cs_change = 1;
status = 0;
list_for_each_entry (t, &m->transfers, transfer_list) { //遍历message 链表上的所有transfer
/* override speed or wordsize? */
if (t->speed_hz || t->bits_per_word)
do_setup = 1;
/* init (-1) or override (1) transfer params */
if (do_setup != 0) {
status = bitbang->setup_transfer(spi, t); //这里对spi的接口进行配置,因为每个transfer都可以设置 bits_per_word
if (status < 0)
break;
if (do_setup == -1)
do_setup = 0;
}
/* set up default clock polarity, and activate chip;
* this implicitly updates clock and spi modes as
* previously recorded for this device via setup().
* (and also deselects any other chip that might be
* selected ...)
*/
if (cs_change) {
bitbang->chipselect(spi, BITBANG_CS_ACTIVE); //这里在imx里没有什么毛线用,拉低电平是通过芯片内部的硬件实现的
ndelay(nsecs); //这个也没有用
}
cs_change = t->cs_change;
if (!t->tx_buf && !t->rx_buf && t->len) {
status = -EINVAL;
break;
}
/* transfer data. the lower level code handles any
* new dma mappings it needs. our caller always gave
* us dma-safe buffers.
*/
if (t->len) {
/* REVISIT dma API still needs a designated
* DMA_ADDR_INVALID; ~0 might be better.
*/
if (!m->is_dma_mapped)
t->rx_dma = t->tx_dma = 0;
status = bitbang->txrx_bufs(spi, t); //在这里把数据发出去了,最终调用了static int spi_imx_transfer(struct spi_device *spi,
struct spi_transfer *transfer)
}
if (status > 0)
m->actual_length += status;
if (status != t->len) {
/* always report some kind of error */
if (status >= 0)
status = -EREMOTEIO;
break;
}
status = 0;
/* protocol tweaks before next transfer */
if (t->delay_usecs)
udelay(t->delay_usecs);
if (!cs_change)
continue;
if (t->transfer_list.next == &m->transfers)
break;
/* sometimes a short mid-message deselect of the chip
* may be needed to terminate a mode or command
*/
ndelay(nsecs);
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
ndelay(nsecs);
}
m->status = status;
m->complete(m->context);
/* normally deactivate chipselect ... unless no error and
* cs_change has hinted that the next message will probably
* be for this chip too.
*/
if (!(status == 0 && cs_change)) {
ndelay(nsecs);
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
ndelay(nsecs);
}
spin_lock_irqsave(&bitbang->lock, flags);
}
bitbang->busy = 0;
spin_unlock_irqrestore(&bitbang->lock, flags);
}
片选与数据发送之间的延迟:
最终是要设置ECSPIx_PERIODREG寄存器的CSD CTL域来实现的,看SC16IS752的时序要求,这里可以不去设置,也有几十多us
最后一步的push是比较复杂的,会产生中断,中断产生后,再接着push
这与ECSPI的操作流程有关
https://blog.csdn.net/droidphone/article/details/24663659