本人是初学者,只为备忘。
SPI驱动分SPI控制器驱动和SPI设备驱动
SPI控制器驱动:以下是代码在arch-lpc32xx.c中。LPC3250有两个SSP控制器(可配置成两个SPI控制器)。它将两个控制器注册成平台设备,但两个控制器使用一个驱动.只有id不一样。
#if defined(CONFIG_SPI_LPC32XX)
#ifdefined(CONFIG_MACH_LPC32XX_SSP0_ENABLE)
static struct resource ssp0_resources[] = {
[0]= {
.start = SSP0_BASE,
.end = SSP0_BASE + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1]= {
.start = IRQ_SSP0,
.end = IRQ_SSP0,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device ssp0_device ={
.name = "spi_lpc32xx",
.id = 0,
.dev = {
.platform_data = &lpc32xx_spi1data,
},
.num_resources = ARRAY_SIZE(ssp0_resources),
.resource = ssp0_resources,
};
#endif
#ifdefined(CONFIG_MACH_LPC32XX_SSP1_ENABLE)
static struct resource ssp1_resources[] = {
[0]= {
.start = SSP1_BASE,
.end = SSP1_BASE + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1]= {
.start = IRQ_SSP1,
.end = IRQ_SSP1,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device ssp1_device ={
.name = "spi_lpc32xx",
.id = 1,
.dev = {
.platform_data = &lpc32xx_spi2data,
},
.num_resources = ARRAY_SIZE(ssp1_resources),
.resource = ssp1_resources,
};
#endif
#endif
通过以上代码,SPI控制器会在系统初始化的时候,通过platform_device_register将它注册进系统。
以上注册完了SPI控制器的device。下一步注册SPI控制器的driver
static struct platform_driverlpc32xx_spi_driver = {
.probe = lpc32xx_spi_probe,
.remove = __devexit_p(lpc32xx_spi_remove),
.driver = {
.name = "spi_lpc32xx",
.owner = THIS_MODULE,
},
};
在Linux 中,每一个类型的驱动都会有一个相应的结构体来描述,这里的spi_master就是
用来描述SPI 主机控制器驱动的,其主要成员是bus_num,cs,spi 模式和时钟设置用到的
函数,数据传输用到的函数等。
分配、注册和注销SPI 主机驱动结构体的API 由SPI 核心层提供:
struct spi_master * spi_alloc_master(structdevice *host, unsigned size);
int spi_register_master(struct spi_master*master);
void spi_unregister_master(structspi_master *master);
使用到platform 总线API 注册到platform
总线上的控制器设备和驱动,都是common 的部分。specific 的部分,会在platform driver
注册后,在probe 函数里面基于注册的common 部分的资源信息来具体实现。称之为
spi_master 的注册部分。
spi_master结构
1. structspi_master {
2. struct device dev;
3.
4. s16 bus_num;//总线编号,从零开始
5.
6. u16 num_chipselect;//支持的片选的数量。从设备的片选号不能大于这个数
7.
8. /* setup mode and clock, etc (spi driver may call many times) */
9. int (*setup)(struct spi_device *spi);//根据spi设备更新硬件配置。
10.
11. int (*transfer)(struct spi_device *spi,struct spi_message *mesg);//添加消息到队列的方法。这个函数不可睡眠。它的职责是安排发生的传送并且调用注册的回调函数complete()。
12.
13. /* called on release() to free memory provided by spi_master */
14. void (*cleanup)(struct spi_device*spi);//cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。
15. };
spi的设备驱动
SPI 设备要挂载到SPI 总线上,这个过程是由SPIcore 提供的一些API 来完成的,
spi_register_driver(),spi_register_device()。
module init 函数中使用spi_register_driver 注册外设驱动,并注册char 设备,导出SPI 操作
API。
1. structspi_driver {
2. int (*probe)(struct spi_device *spi);//和spi匹配成功之后会调用这个方法。因此这个方法需要对设备和私有数据进行初始化。
3. int (*remove)(struct spi_device*spi);//解除spi_device和spi_driver的绑定,释放probe申请的资源。
4. void (*shutdown)(struct spi_device*spi);//关闭
5. int (*suspend)(struct spi_device *spi,pm_message_t mesg);//挂起
6. int (*resume)(struct spi_device*spi);//恢复
7. struct device_driver driver;
8. }
1. structspi_device {
2. struct device dev;
3. struct spi_master *master;//对应的控制器指针
4. u32 max_speed_hz;//spi通信时钟
5. u8 chip_select;//片选号,用来区分同一主控制器上的设备。
6. u8 mode;//各位的定义如下,主要是传输模式、片选极性。
7. #defineSPI_CPHA 0x01 /* clock phase */
8. #defineSPI_CPOL 0x02 /* clock polarity */
9. #defineSPI_MODE_0 (0|0) /* (originalMicroWire) */
10. #defineSPI_MODE_1 (0|SPI_CPHA)
11. #defineSPI_MODE_2 (SPI_CPOL|0)
12. #defineSPI_MODE_3 (SPI_CPOL|SPI_CPHA)
13. #defineSPI_CS_HIGH 0x04 /* chipselect activehigh? */片选电位为高
14. #defineSPI_LSB_FIRST 0x08 /* per-wordbits-on-wire */先输出低比特
15. #defineSPI_3WIRE 0x10 /* SI/SO signals shared*/输入输出共享接口,此时只能做半双工。
16. #defineSPI_LOOP 0x20 /* loopback mode */回写/回显模式
17. u8 bits_per_word;//每个字长的比特数。
18. int irq;//使用到的中断
19. void *controller_state;
20. void *controller_data;
21. char modalias[32];//名字。
22. };
23.
24. ~~~~~~~~~~~~~~~~~~~~~~~
25. 这个结构体描述设备的信息。
26. structspi_board_info {
27. char modalias[32];//设备名
28. const void *platform_data;//平台数据
29. void *controller_data;
30. int irq;//中断
31.
32. /* slower signaling on noisy or low voltage boards */
33. u32 max_speed_hz;//通信时钟
34.
35. u16 bus_num;//总线号
36. u16 chip_select;//片选号
37.
38. u8 mode;//参考spi_device中的成员
39. };
以下是部分spi-lpc32xx.c驱动分析
static int __init lpc32xx_spi_probe(structplatform_device *pdev)
{
structspi_master *master;
structlpc32xxspi *spidat;
structresource *res;
charclkname[16];
intret, irq, i;
/*Get required resources */
res= platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq= platform_get_irq(pdev, 0);
//通过以上语句获得了相应的内存和中断资源。
if((!res) || (irq < 0) | (irq >= NR_IRQS))
{
return-EBUSY;
}
master= spi_alloc_master(&pdev->dev, sizeof(struct lpc32xxspi));
//第二个参数指明要为dev.driver_data分配的空间
if(!master)
{
return-ENODEV;
}
spidat= spi_master_get_devdata(master);
//将spi->master->dev->driver_data 的空间指针传递给spidata
platform_set_drvdata(pdev,master);
//将pdev->dev->driver_data赋为master
//pdev作为spi控制器的common部分,master作为spi控制器的specific部分。
/*Save ID for this device */
spidat->id= pdev->id;
//id指明了使用哪一个控制器
spidat->irq= irq;
spin_lock_init(&spidat->lock);
INIT_WORK(&spidat->work,lpc32xx_work);
//初始化工作队列
INIT_LIST_HEAD(&spidat->queue);
init_waitqueue_head(&spidat->waitq);
//初始化等待队列
spidat->workqueue= create_singlethread_workqueue(master->dev.parent->bus_id);
//创建一个单线程的工作队列
if(!spidat->workqueue)
{
ret= -ENOMEM;
gotoerrout;
}
/*Generate clock name and get clock */
snprintf(clkname,10, "spi%d_ck", spidat->id);
//根据控制器id产生一个时钟名,如spi0_ck
spidat->clk= clk_get(&pdev->dev, clkname);
if(IS_ERR(spidat->clk)) {
ret= -ENODEV;
gotoerrout_qdel;
}
clk_enable(spidat->clk);
/*Save IO resources */
spidat->membase= ioremap(res->start, res->end - res->start + 1);
if(!spidat->membase)
{
ret= -EBUSY;
gotoerrout2;
}
ret= request_irq(spidat->irq, lpc32xx_spi_irq,
IRQF_DISABLED,"spiirq", spidat);
if(ret)
{
ret= -EBUSY;
gotoerrout3;
}
//中断号spidat->irq,这个参数是由pdev传入,要执行的函数指针lpc32xx_spi_irq,
//IRQF_DISABLED,执行中断函数期间,irq被屏蔽
//最后是一个void*类型的指针。
disable_irq(spidat->irq);
master->bus_num= spidat->id;
master->setup= lpc32xx_spi_setup;
master->transfer= lpc32xx_spi_transfer;
/*Is a board specific configuration available? */
spidat->psspcfg= (struct lpc32xx_spi_cfg *) pdev->dev.platform_data;
//platform_data中的数据就是配置数据,但是配置数据放在两个地方,一个在//dev.platform_data中,另一个就是本文件中对struct lpc32xx_spi_cfg的显式初始化。//psspcfg->num_cs代表此时spi控制器中携带的从设备数。
if(spidat->psspcfg == NULL)
{
spidat->psspcfg= &lpc32xx_stdspi_cfg;
}
if(spidat->psspcfg->num_cs < 1)
{
spidat->psspcfg= &lpc32xx_stdspi_cfg;
}
master->num_chipselect= spidat->psspcfg->num_cs;
/*Initialize each chip select and set chip select low */
for(i = 0; i < spidat->psspcfg->num_cs; i++)
{
if(spidat->psspcfg->spi_cs_setup != NULL)
{
spidat->psspcfg->spi_cs_setup(i);
//通过追踪得知,它调用的是static void smartarm3250_spi_cs_setup(int cs)
//此函数仅是将ssel这个引脚置(GPIO_05)为高电平。
}
if(spidat->psspcfg->spi_cs_set != NULL)
{
spidat->psspcfg->spi_cs_set(i,0);
//通过追踪得知,它调用的是static int smartarm3250_spi_cs_set(int cs, int state)
//函数认为cs=1时,设备不存在。state的电平和ssel电平同步。
}
}
/*Initial setup of SPI */
lpc32xx_spi_prep(spidat);
/*Keep the SSP clock off until a transfer is performed to save power */
clk_disable(spidat->clk);
ret= spi_register_master(master);
if(ret)
{
gotoerrout4;
}
return0;
errout4:
free_irq(spidat->irq,pdev);
errout3:
iounmap(spidat->membase);
errout2:
clk_disable(spidat->clk);
clk_put(spidat->clk);
errout_qdel:
destroy_workqueue(spidat->workqueue);
errout:
platform_set_drvdata(pdev,NULL);
spi_master_put(master);
returnret;
}
static void lpc32xx_spi_prep(structlpc32xxspi *spidat)
{
u32tmp;
/*Clear and mask SSP interrupts */
__raw_writel((SSP_ICR_RORIC| SSP_ICR_RTIC), SSP_ICR(spidat->membase));
__raw_writel(0,SSP_IMSC(spidat->membase));
/*Setup default SPI mode */
__raw_writel((SSP_CR0_DSS(16)| SSP_CR0_FRF_SPI | SSP_CR0_CPOL(0) |
SSP_CR0_CPHA(0)| SSP_CR0_SCR(0)), SSP_CR0(spidat->membase));
__raw_writel(SSP_CR1_SSP_ENABLE,SSP_CR1(spidat->membase));
__raw_writel(SSP_CPSR_CPDVSR(2),SSP_CPSR(spidat->membase));
//默认的格式为16位格式,第一种SPI传输模式,主机模式,ssp使能,频率为pclk的2分//频
/*Flush FIFO */
while(__raw_readl(SSP_SR(spidat->membase)) & SSP_SR_RNE)
{
tmp= __raw_readl(SSP_DATA(spidat->membase));
}
//等待接收FIFO为空
/*Controller stays disabled until a transfer occurs */
}
static void lpc32xx_cs_set_state(structspi_device *spi, unsigned int cs,
unsignedint active, unsigned int delay_ns)
{
structlpc32xxspi *spidat = spi_master_get_devdata(spi->master);
intval = (spi->mode & SPI_CS_HIGH) ? active : !active;
if(spidat->psspcfg->spi_cs_set != NULL)
{
spidat->psspcfg->spi_cs_set(cs,val);
}
ndelay(delay_ns);
}
//如果选择了高电平有效,则active和state状态相同,如果没有选择则状态相反。
static int lpc32xx_spi_setup(structspi_device *spi)
{
structlpc32xxspi *spidat = spi_master_get_devdata(spi->master);
unsignedlong flags;
unsignedint bits = spi->bits_per_word;
u32tmp;
if(spi->chip_select > spi->master->num_chipselect)
{
dev_dbg(&spi->dev,
"setup:invalid chipselect %u (%u defined)\n",
spi->chip_select,spi->master->num_chipselect);
return-EINVAL;
}
if(bits == 0)
{
bits= 8;
}
if((bits < 4) || (bits > 16))
{
dev_dbg(&spi->dev,
"setup:invalid bits_per_word %u (8 to 16)\n", bits);
return-EINVAL;
}
if(spi->mode & ~MODEBITS)
{
dev_dbg(&spi->dev,"setup: unsupported mode bits %x\n",
spi->mode& ~MODEBITS);
return-EINVAL;
}
spin_lock_irqsave(&spidat->lock,flags);
clk_enable(spidat->clk);
/*Setup CR0 register */
tmp= SSP_CR0_FRF_SPI;
if(spi->mode & SPI_CPOL)
{
tmp|= SSP_CR0_CPOL(1);
}
if(spi->mode & SPI_CPHA)
{
tmp|= SSP_CR0_CPHA(1);
}
__raw_writel(tmp,SSP_CR0(spidat->membase));
//写入spi控制器的mode
lpc32xx_update_spi_dwidth(spidat,bits);
//写入每帧的位数
lpc32xx_update_spi_clock(spidat,spi->max_speed_hz);
//设置时钟频率
lpc32xx_cs_set_state(spi,spi->chip_select, 0, 0);
//spi->chip_select表示是选中哪一个从机。目前只支持spi->chip_select=0;
//第三个参数为与cs显示的电平关联,最后一个参数是延时。
clk_disable(spidat->clk);
spin_unlock_irqrestore(&spidat->lock,flags);
#if defined (SSP_DEBUG)
dev_dbg(&spi->dev,"SSP (%d) prog rate = %d / "
"actualrate = %d, bits = %d\n", spi->chip_select,
spi->max_speed_hz,spidat->current_speed_hz, spidat->current_bits_wd);
#endif
return0;
}
static irqreturn_t lpc32xx_spi_irq(int irq,void *dev_id)
{
structlpc32xxspi *spidat = dev_id;
/*Disable interrupts for now, do not clear the interrupt states */
__raw_writel(0,SSP_IMSC(spidat->membase));
//禁能中断
wake_up(&spidat->waitq);
//在中断中,唤醒等待队列。
returnIRQ_HANDLED;
}