于是回顾头来看下spi控制器驱动,想从中得到一些信息,下面算是学习笔记,与大家分享下,希望对初学者有帮助。另外,由于个人也是一个初学者,下面的内容只做参考,有误的地方望指教。
先整理下思路,避免看代码的时候晕菜。
主要涉及到的文件如下:
arch/arm/mach-s5pv210/dev-spi.c:平台设备文件
arch/arm/plat-samsung/include/plat/s3c64xx-spi.h:平台头文件,这个文件只是对s3c64xx_spi_info和s3c64xx_spi_csinfo的定义,还有s3c64xx_spi_set_info函数的声明
drivers/spi/spi_s3c64xx.c:控制器驱动文件
drivers/spi/spi.c:spi子系统核心层文件,连接控制器驱动和设备驱动,也是spi总线驱动的实现文件
include/linux/spi/spi.h spi子系统头文件
arch/arm/mach-s5pv210/mach-smdkc110.c:平台文件
这么多文件我们怎么看,我觉得从代码执行流程来看比较好,虽然spi子系统的关联是通过数据结构关联起来的,但是对于初学者,一开始就顺着数据结构的关系来看代码有点吃力。
代码的执行流程,大部分可以通过调试信息来确定。
初看在mach-smdkc110.c文件中有一个machine_init函数,我们肯能觉得一切从这里开始,初始化machine.
但spi.c里的spi_init执行在这之前,然后才是machine_init()在machine_init中回去注册spi平台设备。spi_init为什在在前执行
我们可以看到在spi_init函数实现下面有一条这样的语句postcore_initcall(spi_init);
在init.h#define postcore_initcall(fn) __define_initcall("2",fn,2)
而machine_init是mdesc结构体的一个成员
init_machine = mdesc->init_machine;//setup.c
customize_machine()函数中被调用//setup.c
arch_initcall(customize_machine);arch_initcall被修饰
arch_initcall的实现
#define arch_initcall(fn) __define_initcall("3",fn,3)
__define_initcall的数字参数代表执行顺序,在do_initcalls中被按照顺序调用,由此可以看出是spi_init在machine_init 之前执行
那我们就看spi_init里面做了什么
点击(此处)折叠或打开
-
static int __init spi_init(void)
-
{
-
int status;
-
-
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);//buf for what
-
if (!buf) {
-
status = -ENOMEM;
-
goto err0;
-
}
-
-
status = bus_register(&spi_bus_type);//sys/bus/spi目录和spi目录下的devices和drivers目录。初始化设备链表和驱动链表
-
if (status < 0)
-
goto err1;
-
-
status = class_register(&spi_master_class);//sys/class/spi_master目录
-
if (status < 0)
-
goto err2;
-
return 0;
-
-
err2:
-
bus_unregister(&spi_bus_type);
-
err1:
-
kfree(buf);
-
buf = NULL;
-
err0:
-
return status;
- }
点击(此处)折叠或打开
-
struct bus_type spi_bus_type = {drivers/spi/spi.c
-
.name = "spi",
-
.dev_attrs = spi_dev_attrs,
-
.match = spi_match_device,
-
.uevent = spi_uevent,
-
.suspend = spi_suspend,
-
.resume = spi_resume,
-
};
-
-
static struct class spi_master_class = {drivers/spi/spi.c
-
.name = "spi_master",
-
.owner = THIS_MODULE,
-
.dev_release = spi_master_release,
- };
回答这个问题之前先看下bus_type这个结构体
struct bus_type { <-----------------------------------------------------------------------------
struct bus_type_private *p;--->struct bus_type_private { |
struct kset subsys; ---------------|------------------------------->struct kset {
struct kset *drivers_kset; | struct kobject kobj;----------->struct kobject {
struct kset *devices_kset; | } const char *name;//设置name
struct klist klist_devices; |//传说中的设备链表 struct list_head entry;
struct klist klist_drivers; |//传说中的驱动链表 struct kobject *parent;
struct bus_type *bus;----------- struct kset *kset;
}; struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
}
};
一个目录或文件对应一个kobject
spi_init完后
1在/sys/bus/下创建了一个spi目录spi目录下创建了devices和drivers两个目录和一些设备文件
2初始化设备链表和驱动链表
3 在sys/class/下创建了spi_master目录
----------------------
接着执行的是machine_init()函数
在machine_init 函数中会去注册spi平台设备,其实就是注册两个结构体spi0 和spi1 这里要看你的平台有几个控制器,有几个就注册几个
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));
点击(此处)折叠或打开
-
int platform_add_devices(struct platform_device **devs, int num)
-
{
-
int i, ret = 0;
-
-
for (i = 0; i < num; i++) {
-
ret = platform_device_register(devs[i]);
-
if (ret) {
-
while (--i >= 0)
-
platform_device_unregister(devs[i]);
-
break;
-
}
-
}
-
-
return ret;
- }
static struct platform_device *smdkc110_devices[] __initdata = {
#ifdef CONFIG_S3C64XX_DEV_SPI
&s5pv210_device_spi0,
&s5pv210_device_spi1,
#endif
}
&s5pv210_device_spi0和&s5pv210_device_spi1,这连个结构体就在arch/arm/mach-s5pv210/dev-spi.c
dev-spi.c
点击(此处)折叠或打开
-
static struct resource s5pv210_spi0_resource[] = {
-
[0] = {
-
.start = S5PV210_PA_SPI0,
-
.end = S5PV210_PA_SPI0 + 0x100 - 1,
-
.flags = IORESOURCE_MEM,
-
},
-
[1] = {
-
.start = DMACH_SPI0_TX,
-
.end = DMACH_SPI0_TX,
-
.flags = IORESOURCE_DMA,
-
},
-
[2] = {
-
.start = DMACH_SPI0_RX,
-
.end = DMACH_SPI0_RX,
-
.flags = IORESOURCE_DMA,
-
},
-
[3] = {
-
.start = IRQ_SPI0,
-
.end = IRQ_SPI0,
-
.flags = IORESOURCE_IRQ,
-
},
-
};
-
//在s5pv210_spi_set_info中会对s3c64xx_spi_set_info做进一步的设置
-
static struct s3c64xx_spi_info s5pv210_spi0_pdata = { //平台数据
-
.cfg_gpio = s5pv210_spi_cfg_gpio,
-
.fifo_lvl_mask = 0x1ff,//写缓冲寄存器最大字节数,用来判断是否用DMA
-
.rx_lvl_offset = 15,//读缓冲寄存器最大字节数
-
.high_speed = 1,//是否使用高速模式
-
};
-
-
static u64 spi_dmamask = DMA_BIT_MASK(32);
-
-
struct platform_device s5pv210_device_spi0 = {
-
.name = "s3c64xx-spi",
-
.id = 0,
-
.num_resources = ARRAY_SIZE(s5pv210_spi0_resource),
-
.resource = s5pv210_spi0_resource,
-
.dev = {
-
.dma_mask = &spi_dmamask,//DMA寻址范围
-
.coherent_dma_mask = DMA_BIT_MASK(32),//cache一致性相关
-
.platform_data = &s5pv210_spi0_pdata,//在probe函数中会取出使用
-
},
- };
spi平台设备注册进内核,挂入到平台设备总线的设备总线上。平台总线跟spi总线一样在调用machine_init前便挂入内核了
其实就是创建了一个全局变量,里面有两个内核链表成员,以后注册一个设备就将其插入到这个全局变量的成员链表里
------------------------
之后spi控制器驱动便会去注册,在注册的时候回去到平台设备总线的设备总线上去找刚注册的spi设备。
根据名字匹配到了便会调用probe函数。
刚做的一切都是为了能调用到probe函数服务的,probe函数才是关键,下面来看下spi控制器驱动的probe函数中都做了那些工作
下面是probe的代码,一句句的啃
点击(此处)折叠或打开
-
static int __init s3c64xx_spi_probe(struct platform_device *pdev)
-
{
-
struct resource *mem_res, *dmatx_res, *dmarx_res;
-
struct s3c64xx_spi_driver_data *sdd;
-
struct s3c64xx_spi_info *sci;
-
struct spi_master *master;
-
int ret;
-
-
if (pdev->id < 0) {//判断平台设备的id是否合法,在平台设备结构体定义的时候已经初始化了
-
dev_err(&pdev->dev,
-
"Invalid platform device id-%d\n", pdev->id);
-
return -ENODEV;
-
}
-
-
if (pdev->dev.platform_data == NULL) {//平台设备私有数据是否为空,在平台设备结构体定义的时候初始化,在板级文件里调用s5pv210_spi_set_info函数进一步填充
-
dev_err(&pdev->dev, "platform_data missing!\n");
-
return -ENODEV;
-
}
-
-
/* Check for availability of necessary resource */
-
-
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);//获取dma写资源
-
if (dmatx_res == NULL) {
-
dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n");
-
return -ENXIO;
-
}
-
-
dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);//获取dma读资源
-
if (dmarx_res == NULL) {
-
dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource\n");
-
return -ENXIO;
-
}
-
-
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取内存资源
-
if (mem_res == NULL) {
-
dev_err(&pdev->dev, "Unable to get SPI MEM resource\n");
-
return -ENXIO;
-
}
-
-
master = spi_alloc_master(&pdev->dev,
-
sizeof(struct s3c64xx_spi_driver_data));//构建spi_master结构体,代表一个控制器
-
点击(此处)折叠或打开
-
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
-
{
-
struct spi_master *master;
-
-
if (!dev)
-
return NULL;
-
-
master = kzalloc(size + sizeof *master, GFP_KERNEL);
-
if (!master)
-
return NULL;
-
-
device_initialize(&master->dev);
-
master->dev.class = &spi_master_class;
-
master->dev.parent = get_device(dev);
-
spi_master_set_devdata(master, &master[1]);//设置devdata,待会儿取出
-
-
return master;
- }
-
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
-
if (master == NULL) {
-
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
-
return -ENOMEM;
-
}
-
-
sci = pdev->dev.platform_data;//取出平台数据
-
-
platform_set_drvdata(pdev, master);//master作为平台设备的私有数据,dev->p->driver_data = master;
-
-
sdd = spi_master_get_devdata(master);//将上一步设置的数据拿出,下面是对这个结构体的填充(上一步是对指针赋值,这一步是为这个指针娶个临时的名字,让后通过这个名字去访问它)
-
sdd->master = master;
-
sdd->cntrlr_info = sci;//平台数据
-
sdd->pdev = pdev;//属于那个平台设备
-
sdd->sfr_start = mem_res->start;//内存起始地址
-
sdd->tx_dmach = dmatx_res->start;//dma写起始地址
-
sdd->rx_dmach = dmarx_res->start;dma读起始地址
-
-
sdd->cur_bpw = 8;//传输时每个word的位数
-
-
master->bus_num = pdev->id;//控制器编号
-
master->setup = s3c64xx_spi_setup;//spi控制器设置函数指针
-
master->transfer = s3c64xx_spi_transfer;//控制器发送数据函数指针
-
master->num_chipselect = sci->num_cs;//num_ns在函数s5pv210_spi_set_info(dev-spi.c)中被赋值,在machine_init函数中调用(mach-smdkc110.c),意思为控制器上挂载从设备的个数
-
master->dma_alignment = 8;//DMA相关
-
/* the spi->mode bits understood by this driver: */
-
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;//传输方式设置
- --------------------------------------
-
if (request_mem_region(mem_res->start,
-
resource_size(mem_res), pdev->name) == NULL) {
-
dev_err(&pdev->dev, "Req mem region failed\n");
-
ret = -ENXIO;
-
goto err0;
-
}
-
-
sdd->regs = ioremap(mem_res->start, resource_size(mem_res));
-
if (sdd->regs == NULL) {
-
dev_err(&pdev->dev, "Unable to remap IO\n");
-
ret = -ENXIO;
-
goto err1;
-
}
-
-
if (sci->cfg_gpio == NULL || sci->cfg_gpio(pdev)) {
-
dev_err(&pdev->dev, "Unable to config gpio\n");
-
ret = -EBUSY;
-
goto err2;
-
}
-
-------------------------------------
- 以上都是申请资源
-
/* Setup clocks */
-
sdd->clk = clk_get(&pdev->dev, "spi");//从pdev->dev中获取clk,clk在哪里设置?
-
if (IS_ERR(sdd->clk)) {
-
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
-
ret = PTR_ERR(sdd->clk);
-
goto err3;
-
}
-
-
if (clk_enable(sdd->clk)) {
-
dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
-
ret = -EBUSY;
-
goto err4;
-
}
-
-
sdd->src_clk = clk_get(&pdev->dev, sci->src_clk_name);//pdev->dev->platformdata有一个成员src_clk_name,在s5pv210_spi_set_info里设置,在machine_init里调用
-
if (IS_ERR(sdd->src_clk)) {
-
dev_err(&pdev->dev,
-
"Unable to acquire clock '%s'\n", sci->src_clk_name);
-
ret = PTR_ERR(sdd->src_clk);
-
goto err5;
-
}
-
-
if (clk_enable(sdd->src_clk)) {
-
dev_err(&pdev->dev, "Couldn't enable clock '%s'\n",
-
sci->src_clk_name);
-
ret = -EBUSY;
-
goto err6;
-
}
-
-
sdd->workqueue = create_singlethread_workqueue(
-
dev_name(master->dev.parent));//创建工作队列线程,master->dev.parent就是pdev->dev(平台设备的dev)
-
if (sdd->workqueue == NULL) {
-
dev_err(&pdev->dev, "Unable to create workqueue\n");
-
ret = -ENOMEM;
-
goto err7;
-
}
-
-
/* Setup Deufult Mode */
-
s3c64xx_spi_hwinit(sdd, pdev->id);//spi硬件初始化
-
点击(此处)折叠或打开
-
static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel)
-
{
-
struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
-
void __iomem *regs = sdd->regs;
-
unsigned int val;
-
-
sdd->cur_speed = 0;
-
-
S3C64XX_SPI_DEACT(sdd);//#define S3C64XX_SPI_DEACT(c) writel(S3C64XX_SPI_SLAVE_SIG_INACT, (c)->regs + S3C64XX_SPI_SLAVE_SEL)//不片选
-
-
/* Disable Interrupts - we use Polling if not DMA mode */
-
writel(0, regs + S3C64XX_SPI_INT_EN);//all interrupt disable
-
-
writel(sci->src_clk_nr << S3C64XX_SPI_CLKSEL_SRCSHFT,
-
regs + S3C64XX_SPI_CLK_CFG);//时钟源头的选择,CLK_CFG0的第10位,0表示PCLK,1表示EXT_CLK为时钟源
-
writel(0, regs + S3C64XX_SPI_MODE_CFG);//传输模式配置寄存器设置
-
writel(0, regs + S3C64XX_SPI_PACKET_CNT);
-
-
/* Clear any irq pending bits */
-
writel(readl(regs + S3C64XX_SPI_PENDING_CLR),
-
regs + S3C64XX_SPI_PENDING_CLR);
-
-
writel(0, regs + S3C64XX_SPI_SWAP_CFG);
-
-
val = readl(regs + S3C64XX_SPI_MODE_CFG);
-
val &= ~S3C64XX_SPI_MODE_4BURST;
-
val &= ~(S3C64XX_SPI_MAX_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF);
-
val |= (S3C64XX_SPI_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF);
-
writel(val, regs + S3C64XX_SPI_MODE_CFG);//以上都是控制器的配置,内核一般都会自带,不用我们写,若要移植到不同的平台,才需要相应的修改
-
-
flush_fifo(sdd);//清空rx tx buffer
- }
-
static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel)
-
spin_lock_init(&sdd->lock);
-
init_completion(&sdd->xfer_completion);
-
INIT_WORK(&sdd->work, s3c64xx_spi_work);
-
INIT_LIST_HEAD(&sdd->queue);//以上是初始化工作队列
-
-
if (spi_register_master(master)) {//注册master
-
dev_err(&pdev->dev, "cannot register SPI master\n");
-
ret = -EBUSY;
-
goto err8;
-
}
-
点击(此处)折叠或打开
-
int spi_register_master(struct spi_master *master)
-
{
-
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
-
struct device *dev = master->dev.parent;
-
int status = -ENODEV;
-
int dynamic = 0;
-
-
if (!dev)
-
return -ENODEV;
-
-
/* even if it's just one always-selected device, there must
-
* be at least one chipselect
-
*/
-
if (master->num_chipselect == 0)
-
return -EINVAL;
-
-
/* convention: dynamically assigned bus IDs count down from the max */
-
if (master->bus_num < 0) {
-
/* FIXME switch to an IDR based scheme, something like
-
* I2C now uses, so we can't run out of "dynamic" IDs
-
*/
-
master->bus_num = atomic_dec_return(&dyn_bus_id);
-
dynamic = 1;
-
}
-
-
/* register the device, then userspace will see it.
-
* registration fails if the bus ID is in use.
-
*/
-
dev_set_name(&master->dev, "spi%u", master->bus_num);//spi0
-
status = device_add(&master->dev);//注册master
-
if (status < 0)
-
goto done;
-
dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
-
dynamic ? " (dynamic)" : "");
-
-
/* populate children from any spi device tables */
- scan_boardinfo(master);//扫描device链表,在registered_spi_board_info创建的(machien_init函数中调用)
-
点击(此处)折叠或打开
-
static void scan_boardinfo(struct spi_master *master)
-
{
-
struct boardinfo *bi;
-
-
mutex_lock(&board_lock);
-
list_for_each_entry(bi, &board_list, list) {//扫描board_list链表,取出在register_spi_board_info放入的s3c64xx_board_info,来构建master_device
-
struct spi_board_info *chip = bi->board_info;
-
unsigned n;
-
-
for (n = bi->n_board_info; n > 0; n--, chip++) {
-
if (chip->bus_num != master->bus_num)//根据总线号找
-
continue;
-
/* NOTE: this relies on spi_new_device to
-
* issue diagnostics when given bogus inputs
-
*/
-
(void) spi_new_device(master, chip);
-
点击(此处)折叠或打开
-
struct spi_device *spi_new_device(struct spi_master *master,
-
struct spi_board_info *chip)
-
{
-
struct spi_device *proxy;
-
int status;
-
-
/* NOTE: caller did any chip->bus_num checks necessary.
-
*
-
* Also, unless we change the return value convention to use
-
* error-or-pointer (not NULL-or-pointer), troubleshootability
-
* suggests syslogged diagnostics are best here (ugh).
-
*/
-
-
proxy = spi_alloc_device(master);//分配一个spi_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;
-
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);//注册spi_device
-
if (status < 0) {
-
spi_dev_put(proxy);
-
return NULL;
-
}
-
-
return proxy;
- }
-
struct spi_device *spi_new_device(struct spi_master *master,
-
}
-
}
-
mutex_unlock(&board_lock);
- }
-
static void scan_boardinfo(struct spi_master *master)
-
status = 0;
-
done:
-
return status;
- }
-
int spi_register_master(struct spi_master *master)
-
dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d "
-
"with %d Slaves attached\n",
-
pdev->id, master->num_chipselect);
-
dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
-
mem_res->end, mem_res->start,
- sdd->rx_dmach, sdd->tx_dmach);
-
return ret;
- }
当有设备驱动加载时就可以到spi总线上去匹配spi设备
剩下的就是终极目标数据是怎样传输的,因为上面的总总一切只为了传输数据