SPI子系统分析之四:驱动模块
内核版本:3.9.5
SPI控制器层(平台相关)
上一节讲了SPI核心层的注册和匹配函数,它是平台无关的.正是在核心层抽象了SPI控制器层的相同部分然后提供了统一的API给SPI设备层来使用.我们这一节就能看到,一个SPI控制器以platform_device的形式注册进内核,并且调用spi_register_board_info函数注册了spi_board_info结构.我们前面说过,struct spi_board_info结构是对spi_device的描述,其中的内从最终是要用来初始化struct spi_device实例的.
哎!闲话少说了,越说越糊涂.我们以davinci的dm365平台为例,来看看SPI控制器的相关内容.在arch/arm/mach-davinci/board-dm365-evm.c中有:
static struct spi_board_info dm365_evm_spi_info[] __initconst = {
{
.modalias = "at25",
.platform_data = &at25640,
.max_speed_hz = * * ,
.bus_num = ,
.chip_select = ,
.mode = SPI_MODE_0,
},
}; static __init void dm365_evm_init(void)
{
……
dm365_init_spi0(BIT(), dm365_evm_spi_info,
ARRAY_SIZE(dm365_evm_spi_info));
}
dm365_evm_init这个函数是dm365平台初始化函数,我略去了和SPI无关的部分.可以看到其中调用了dm365_init_spi0函数,并且将一个struct spi_board_info这个结构类型的数组作为参数传了进去.那么来看看dm365_init_spi0函数,在arch/arm/mach-davinci/dm365.c中:
static u64 dm365_spi0_dma_mask = DMA_BIT_MASK(); static struct davinci_spi_platform_data dm365_spi0_pdata = {
.version = SPI_VERSION_1,
.num_chipselect = ,
.dma_event_q = EVENTQ_3,
}; static struct resource dm365_spi0_resources[] = {
{
.start = 0x01c66000,
.end = 0x01c667ff,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_DM365_SPIINT0_0,
.flags = IORESOURCE_IRQ,
},
{
.start = ,
.flags = IORESOURCE_DMA,
},
{
.start = ,
.flags = IORESOURCE_DMA,
},
}; static struct platform_device dm365_spi0_device = {
.name = "spi_davinci",/*这个是和platform_driver匹配的依据,具体到davinci的板子就是davinci_spi_driver*/
.id = ,/*对于SPI,这个值最后会在初始化spi_master的时候用来初始化master->bus_num*/
.dev = {
.dma_mask = &dm365_spi0_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(),
.platform_data = &dm365_spi0_pdata,
},
.num_resources = ARRAY_SIZE(dm365_spi0_resources),
.resource = dm365_spi0_resources,
}; void __init dm365_init_spi0(unsigned chipselect_mask,
const struct spi_board_info *info, unsigned len)
{
davinci_cfg_reg(DM365_SPI0_SCLK);
davinci_cfg_reg(DM365_SPI0_SDI);
davinci_cfg_reg(DM365_SPI0_SDO); /* not all slaves will be wired up */
if (chipselect_mask & BIT())
davinci_cfg_reg(DM365_SPI0_SDENA0);
if (chipselect_mask & BIT())
davinci_cfg_reg(DM365_SPI0_SDENA1); spi_register_board_info(info, len); platform_device_register(&dm365_spi0_device);
}
第54行注册了struct spi_board_info实例,就是我们传进来的dm365_evm_spi_info.在设备移植时填充结构体spi_board_info是移植的重要工作.我们来看看这个函数的实现,在drivers/spi/spi.c中:
int spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
struct boardinfo *bi;
int i; bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);/*为结构体boardinfo分配内存空间*/
if (!bi)
return -ENOMEM; for (i = ; i < n; i++, bi++, info++) {
struct spi_master *master; memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list);/*添加到板级描述符链表*/
list_for_each_entry(master, &spi_master_list, list)/*将SPI主机控制类链表所有的节点匹配板级信息的设备初始化*/
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
} return ;
}
这里又看到了一个结构struct boardinfo,其实它简单的就像没穿裤子的女人,我们来看,在同文件中:
struct boardinfo {
struct list_head list;
struct spi_board_info board_info;
}; static LIST_HEAD(board_list);
static LIST_HEAD(spi_master_list); /*
* Used to protect add/del opertion for board_info list and
* spi_master list, and their matching process
*/
/*boardinfo链表操作锁*/
static DEFINE_MUTEX(board_lock);
这个结构是一个板级相关信息链表,就是说它是一些描述spi_device的信息的集合.结构体boardinfo管理多个结构体spi_board_info,结构体spi_board_info中挂在SPI总线上的设备的平台信息.一个结构体spi_board_info对应着一个SPI设备spi_device.
同时我们也看到了,函数中出现的board_list和spi_master_list都是全局的链表,她们分别记录了系统中所有的boardinfo和所有的spi_master.至于spi_match_master_to_boardinfo函数是什么意思,我们后面还会遇到,到时候再讲.
dm365_init_spi0函数中第56行注册平台设备.我们看到这个platform_device的的name是"spi_davinci",那么就必然还存在一个名为"spi_davinci"的platform_driver.那好办了,搜一下发现在drivers/spi/spi_davinci.c中:
static struct platform_driver davinci_spi_driver = {
.driver = {
.name = "spi_davinci",
.owner = THIS_MODULE,
.of_match_table = davinci_spi_of_match,
},
.probe = davinci_spi_probe,
.remove = davinci_spi_remove,
};
module_platform_driver(davinci_spi_driver);
Linux设备模型常识告诉我们,当系统中注册了一个名为"spi_davinci"的platform_device时,同时又住了一个名为"spi_davinci"的platform_driver.那么就会执行这里的probe回调.这里我们来看davinci_spi_probe函数.
static int davinci_spi_probe(struct platform_device *pdev)
{
struct spi_master *master;
struct davinci_spi *dspi;/*davinci_spi这个结构用来描述具体的davinci平台上的spi控制器,等于说是对spi_master的一个封装*/
struct davinci_spi_platform_data *pdata;
struct resource *r, *mem;
resource_size_t dma_rx_chan = SPI_NO_RESOURCE;
resource_size_t dma_tx_chan = SPI_NO_RESOURCE;
int i = , ret = ;
u32 spipc0; /*分配master结构体,其中包括davinci_spi结构的内存空间,使用master.dev.driver_data指向它*/
master = spi_alloc_master(&pdev->dev, sizeof(struct davinci_spi));
if (master == NULL) {
ret = -ENOMEM;
goto err;
} dev_set_drvdata(&pdev->dev, master);/*pdev->dev.device_private->driver_data = master*/ dspi = spi_master_get_devdata(master);/*就是获取上文master.dev.driver_data指向的对象地址,其实就是davinci_spi结构对象的空间地址,将
其赋给dspi*/
if (dspi == NULL) {/*dspi不能为空哦*/
ret = -ENOENT;
goto free_master;
} /*下面这几行就是填充dspi的pdata字段*/
if (pdev->dev.platform_data) {
pdata = pdev->dev.platform_data;/*具体到对于dm365来说就是dm365_spi0_pdata*/
dspi->pdata = *pdata;
} else {
/* update dspi pdata with that from the DT */
ret = spi_davinci_get_pdata(pdev, dspi);
if (ret < )
goto free_master;
} /* pdata in dspi is now updated and point pdata to that */
pdata = &dspi->pdata;/*pdata指针再指向dspi->pdata*/ r = platform_get_resource(pdev, IORESOURCE_MEM, );/*获取IO资源*/
if (r == NULL) {
ret = -ENOENT;
goto free_master;
} dspi->pbase = r->start; mem = request_mem_region(r->start, resource_size(r), pdev->name);/*申请IO内存*/
if (mem == NULL) {
ret = -EBUSY;
goto free_master;
} dspi->base = ioremap(r->start, resource_size(r));/*建立内存映射*/
if (dspi->base == NULL) {
ret = -ENOMEM;
goto release_region;
} dspi->irq = platform_get_irq(pdev, );/*获取irq号*/
if (dspi->irq <= ) {
ret = -EINVAL;
goto unmap_io;
} ret = request_threaded_irq(dspi->irq, davinci_spi_irq, dummy_thread_fn,
, dev_name(&pdev->dev), dspi);/*申请spi中断,中断处理函数为davinci_spi_irq*/
if (ret)
goto unmap_io; /*设置bitbang的所属master*/
dspi->bitbang.master = spi_master_get(master);
if (dspi->bitbang.master == NULL) {
ret = -ENODEV;
goto irq_free;
} dspi->clk = clk_get(&pdev->dev, NULL);/*获取spi时钟*/
if (IS_ERR(dspi->clk)) {
ret = -ENODEV;
goto put_master;
}
clk_prepare_enable(dspi->clk); master->dev.of_node = pdev->dev.of_node;
master->bus_num = pdev->id;/*bus_num*/
master->num_chipselect = pdata->num_chipselect;/*保存SPI主机控制器支持的片选数量.具体到dm365可以看到dm365_spi0_pdata中将其定义为2*/
master->setup = davinci_spi_setup; /*设置bitbang控制传输的相关函数*/
dspi->bitbang.chipselect = davinci_spi_chipselect;
dspi->bitbang.setup_transfer = davinci_spi_setup_transfer; dspi->version = pdata->version;/*具体到dm365可以看到dm365_spi0_pdata中将其定义为0*/ dspi->bitbang.flags = SPI_NO_CS | SPI_LSB_FIRST | SPI_LOOP;
if (dspi->version == SPI_VERSION_2)
dspi->bitbang.flags |= SPI_READY; r = platform_get_resource(pdev, IORESOURCE_DMA, );/*获取DMA资源,这作为输入缓冲*/
if (r)
dma_rx_chan = r->start;
r = platform_get_resource(pdev, IORESOURCE_DMA, );/*由参数就能知道davinci的DMA资源定义了两个,这里就获取第二个.这作为输出缓冲*/
if (r)
dma_tx_chan = r->start; dspi->bitbang.txrx_bufs = davinci_spi_bufs;/*传输数据最终要调用的函数*/
if (dma_rx_chan != SPI_NO_RESOURCE &&
dma_tx_chan != SPI_NO_RESOURCE) {
dspi->dma_rx_chnum = dma_rx_chan;
dspi->dma_tx_chnum = dma_tx_chan; ret = davinci_spi_request_dma(dspi);
if (ret)
goto free_clk; dev_info(&pdev->dev, "DMA: supported\n");
dev_info(&pdev->dev, "DMA: RX channel: %d, TX channel: %d, "
"event queue: %d\n", dma_rx_chan, dma_tx_chan,
pdata->dma_event_q);
} dspi->get_rx = davinci_spi_rx_buf_u8;
dspi->get_tx = davinci_spi_tx_buf_u8; init_completion(&dspi->done);/*初始化completion,用于实现同步I/O*/ /* Reset In/OUT SPI module */
iowrite32(, dspi->base + SPIGCR0);
udelay();
iowrite32(, dspi->base + SPIGCR0); /* Set up SPIPC0. CS and ENA init is done in davinci_spi_setup */
spipc0 = SPIPC0_DIFUN_MASK | SPIPC0_DOFUN_MASK | SPIPC0_CLKFUN_MASK;
iowrite32(spipc0, dspi->base + SPIPC0); /* initialize chip selects */
if (pdata->chip_sel) {/*davinci的pdata中chip_sel字段并没有设置,这里为空,因此不会进来*/
for (i = ; i < pdata->num_chipselect; i++) {
if (pdata->chip_sel[i] != SPI_INTERN_CS)
gpio_direction_output(pdata->chip_sel[i], );
}
} if (pdata->intr_line)/*dm365这个字段为空*/
iowrite32(SPI_INTLVL_1, dspi->base + SPILVL);
else
iowrite32(SPI_INTLVL_0, dspi->base + SPILVL); iowrite32(CS_DEFAULT, dspi->base + SPIDEF); /* master mode default */
set_io_bits(dspi->base + SPIGCR1, SPIGCR1_CLKMOD_MASK);
set_io_bits(dspi->base + SPIGCR1, SPIGCR1_MASTER_MASK);/*默认设置SPI主控制器工作在master方式*/
set_io_bits(dspi->base + SPIGCR1, SPIGCR1_POWERDOWN_MASK); ret = spi_bitbang_start(&dspi->bitbang);/*注册我们的主机SPI控制器*/
if (ret)
goto free_dma; dev_info(&pdev->dev, "Controller at 0x%p\n", dspi->base); return ret; free_dma:
dma_release_channel(dspi->dma_rx);
dma_release_channel(dspi->dma_tx);
free_clk:
clk_disable_unprepare(dspi->clk);
clk_put(dspi->clk);
put_master:
spi_master_put(master);/*减少引用计数*/
irq_free:
free_irq(dspi->irq, dspi);
unmap_io:
iounmap(dspi->base);
release_region:
release_mem_region(dspi->pbase, resource_size(r));
free_master:
kfree(master);
err:
return ret;
}
该函数首先为spi_master结构体以及davinci_spi结构体分配了空间,同时,spi_master.dev.driver_data指向了davinci_spi.接着执行了该条语句:
pdata = pdev->dev.platform_data;/*具体到对于dm365来说就是dm365_spi0_pdata*/
dspi->pdata = *pdata;
NOTE:在这里获取platform_device.dev.platform_data,也就是平台设备的相关数据,这是平台设备移植最需要关注的地方.
随后,为master定义了setup方法,为bitbang定义了3个方法.之后获取了一系列的资源,同时注册了中断服务程序.接着再初始化了completion,这个东东将用于实现同步I/O,他的伟大之处后面会体现出来的.最后调用spi_bitbang_start注册主机控制器.我们来看这个函数,在drivers/spi/spi_bitbang.c中:
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
struct spi_master *master = bitbang->master;
int status; if (!master || !bitbang->chipselect)
return -EINVAL; INIT_WORK(&bitbang->work, bitbang_work);/*初始化一个struct work,处理函数为bitbang_work*/
spin_lock_init(&bitbang->lock);/*初始化自旋锁*/
INIT_LIST_HEAD(&bitbang->queue);/*初始化链表头,链表为双向循环链表*/ if (!master->mode_bits)
master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags; /*检测bitbang中的函数是否都定义了,如果没定义,则默认使用spi_bitbang_xxx*/
if (!master->transfer)/*master的transfer方法没有定义过*/
master->transfer = spi_bitbang_transfer;/*使用默认的spi_bitbang_transfe方法*/
if (!bitbang->txrx_bufs) {/*如果bitbang没有txrx_bufs方法,其实对于davinci在davinci_spi_probe函数中定义过该方法*/
bitbang->use_dma = ;
bitbang->txrx_bufs = spi_bitbang_bufs;
if (!master->setup) {
if (!bitbang->setup_transfer)
bitbang->setup_transfer =
spi_bitbang_setup_transfer;
master->setup = spi_bitbang_setup;
master->cleanup = spi_bitbang_cleanup;
}
} else if (!master->setup)/*对于davinci在davinci_spi_probe函数中定义过该方法*/
return -EINVAL;
if (master->transfer == spi_bitbang_transfer &&
!bitbang->setup_transfer)
return -EINVAL; /* this task is the only thing to touch the SPI bits */
bitbang->busy = ;
bitbang->workqueue = create_singlethread_workqueue(
dev_name(master->dev.parent));/*创建bitbang的工作队列*/
if (bitbang->workqueue == NULL) {
status = -EBUSY;
goto err1;
} /* driver may get busy before register() returns, especially
* if someone registered boardinfo for devices
*/
status = spi_register_master(master);/*注册spi控制器*/
if (status < )
goto err2; return status; err2:
destroy_workqueue(bitbang->workqueue);
err1:
return status;
}
EXPORT_SYMBOL_GPL(spi_bitbang_start);
定义了控制器的transfer方法为spi_bitbang_transfer.创建了一个工作队列和一个工作bitbang_work,同时创建了一个链表.这些东东后面都会看到.最后,调用了spi_register_master函数,该函数将完成SPI控制器的注册,其中还牵涉到spi_device的注册.我们来看看这个函数.下列函数位于drivers/spi/spi.c:
int spi_register_master(struct spi_master *master)
{
static atomic_t dyn_bus_id = ATOMIC_INIT((<<) - );
struct device *dev = master->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
int dynamic = ; if (!dev)
return -ENODEV; status = of_spi_register_master(master);
if (status)
return status; /* even if it's just one always-selected device, there must
* be at least one chipselect
*/
if (master->num_chipselect == )/*SPI主控制器支持的片选数当然不能为0,否则还怎么挂接从设备啊.一个接口对应一个master,一个master对应
一条SPI总线,一条总线上可能挂有多个设备,num_chipselect就表示该总线上的设备数*/
return -EINVAL; if ((master->bus_num < ) && master->dev.of_node)
master->bus_num = of_alias_get_id(master->dev.of_node, "spi"); /* convention: dynamically assigned bus IDs count down from the max */
if (master->bus_num < ) {/*总线号从最大开始减*/
/* 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 = ;
} spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
master->bus_lock_flag = ;/*这个标志指示SPI总线是否被锁*/ /* 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);
status = device_add(&master->dev);/*向内核注册设备*/
if (status < )
goto done;
dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
dynamic ? " (dynamic)" : ""); /* If we're using a queued driver, start the queue */
if (master->transfer)/*对于具体到davinci,此字段在spi_bitbang_start中被初始化为spi_bitbang_transfer*/
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
status = spi_master_initialize_queue(master);
if (status) {
device_unregister(&master->dev);
goto done;
}
} mutex_lock(&board_lock);
list_add_tail(&master->list, &spi_master_list);/*把这个SPI主机控制器添加进全局的spi_master_list链表*/
list_for_each_entry(bi, &board_list, list)/*遍历全局的board_list链表,为每一个boardinfo结构节点查找其中的指向的spi_board_info结构,通过
对spi_board_info的bus_bum和SPI主机控制器(spi_master)的bus_num进行匹配,来确定SPI从设备是否由此SPI主机控制器来控制.如果匹配,则通
过调用spi_new_device函数创建spi_device从设备,并且将其注册进内核*/
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock); /* Register devices from the device tree and ACPI */
of_register_spi_devices(master);
acpi_register_spi_devices(master);
done:
return status;
}
EXPORT_SYMBOL_GPL(spi_register_master);
该函数注释一目了然,我们来看看spi_match_master_to_boardinfo这个函数吧.在同文件中有:
/*使用SPI主控制类和板级信息匹配则添加一个新设备*/
static void spi_match_master_to_boardinfo(struct spi_master *master,
struct spi_board_info *bi)
{
struct spi_device *dev; if (master->bus_num != bi->bus_num)/*通过bus_num对spi设备和master进行匹配*/
return; dev = spi_new_device(master, bi);/*执行到此,表示匹配完成,SPI设备由该SPI接口来控制,开始创建spi_device*/
if (!dev)
dev_err(master->dev.parent, "can't create new device for %s\n",
bi->modalias);
}
地球人都知道这段代码什么意思,好了继续看spi_new_device函数.在同文件中:
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结构,并初始化一些字段*/
if (!proxy)
return NULL; WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias)); /*从spi_board_info获取SPI从设备的参数*/
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);/*将新设备添加进内核*/
if (status < ) {
spi_dev_put(proxy);/*从内核模块中撤销这个SPI(从)设备,但是这貌似并没有释放spi_alloc_device开辟的内存.实质上这个函数只是减少
了SPI(从)设备的引用计数*/
return NULL;
} return proxy;
}
EXPORT_SYMBOL_GPL(spi_new_device);
这个函数首先创建了spi_device结构,让后通过板级信息spi_board_info将SPI从设备的相关信息复制给spi_device结构,从而完成了spi_device结构的定义,最后调用spi_add_device,完成spi_device的注册.其中struct spi_board_info *chip这就是我们当初arch/arm/mach-davinci/board-dm365-evm.c中定义的dm365_evm_spi_info数组中的结构实例.
第25行我们就知道,这里注册的spi_device的modalias字段就被初始化为"at25".那么与其对应的spi_driver的device_driver中的name字段肯定为"at25".只有这样才能在SPI核心层的spi_match_device函数中匹配.搜了一遍内核,看到在drivers/msic/eeprom/at25.c中有:
static struct spi_driver at25_driver = {
.driver = {
.name = "at25",
.owner = THIS_MODULE,
},
.probe = at25_probe,/*与相应的SPI(从)设备spi_device匹配成功后,则调用这里的probe函数*/
.remove = at25_remove,
}; module_spi_driver(at25_driver);
第10行这个宏是SPI架构专门定义的,在include/linux/spi/spi.h中,我们来看:
#define module_spi_driver(__spi_driver) \
module_driver(__spi_driver, spi_register_driver, \
spi_unregister_driver)
让暴风雨来的更猛烈些吧,研究内核的孩纸都伤不起啊,我们只有硬着头皮往下看,在include/linux/device.h中:
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
山重水复疑无路,柳暗花明又一村.这东东你让我说啥~没说的.相信大家也不想在听我唠叨.但是这里为什么多此一举写了这么一个叫宏,而不是像其他的linux模块那样直接写两个module_xxx呢?这是因为这个设备eeprom本身是不可插拔的,也就不需要什么加载卸载的过程,系统上电运行直接就注册了.
那么我们知道了,现在因为SPI子系统核心层我们已经注册了一条SPI总线,就是spi_bus_type.它里面的match回调函数我们已经看过了,就是spi_match_device.就是在这个函数中将完成这个spi_device和spi_driver的匹配,匹配成功就会去执行spi_driver的probe回调了.我们来看,at25_probe函数在drivers/msic/eeprom/at25.c中:
static int at25_probe(struct spi_device *spi)
{
struct at25_data *at25 = NULL;/*这个结构其实就是对spi_device的封装,我们可以像理解面向对象那样将这个结构理解为对spi_device的实例*/
struct spi_eeprom chip;/*此结构用来作为记录一个SPI EEPROMS的句柄,它保存了platform_data的数据*/
struct device_node *np = spi->dev.of_node;
int err;
int sr;
int addrlen; /* Chip description */
if (!spi->dev.platform_data) {/*具体到dm365平台,此platform_data就是arch/arm/mach-davinci/board-ddm365-evm.c中定义的的at25640*/
if (np) {
err = at25_np_to_chip(&spi->dev, np, &chip);
if (err)
goto fail;
} else {
dev_err(&spi->dev, "Error: no chip description\n");
err = -ENODEV;
goto fail;
}
} else
chip = *(struct spi_eeprom *)spi->dev.platform_data; /* For now we only support 8/16/24 bit addressing */
if (chip.flags & EE_ADDR1)/*flags用来标志eeprom的位宽和读写模式,具体到dm365平台此flags为EE_ADDR2*/
addrlen = ;
else if (chip.flags & EE_ADDR2)
addrlen = ;
else if (chip.flags & EE_ADDR3)
addrlen = ;
else {
dev_dbg(&spi->dev, "unsupported address type\n");
err = -EINVAL;
goto fail;
} /* Ping the chip ... the status register is pretty portable,
* unlike probing manufacturer IDs. We do expect that system
* firmware didn't write it in the past few milliseconds!
*/
/*ping一下芯片,状态寄存器是很容易被检测的,不像制造商ID那样麻烦.我们期待系统固件之前没有写入它.*/
sr = spi_w8r8(spi, AT25_RDSR);/*同步的读取状态寄存器的值,返回的八位数据保存在sr中.spi_w8r8这个函数有可能会睡眠*/
if (sr < || sr & AT25_SR_nRDY) {
dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);
err = -ENXIO;
goto fail;
} if (!(at25 = kzalloc(sizeof *at25, GFP_KERNEL))) {/*以kmalloc分配内存,并清0*/
err = -ENOMEM;
goto fail;
} mutex_init(&at25->lock);/*初始化互斥体*/
at25->chip = chip;/*记录下spi_eeprom*/
at25->spi = spi_dev_get(spi);/*记录下这个片子对应的spi_device*/
dev_set_drvdata(&spi->dev, at25);/*spi->dev.device_private->driver_data = at25*/
at25->addrlen = addrlen;/*我觉得应该可以理解为这个片子使用的位宽是多少个字节.那根据上文分析,此处值为2*/ /* Export the EEPROM bytes through sysfs, since that's convenient.
* And maybe to other kernel code; it might hold a board's Ethernet
* address, or board-specific calibration data generated on the
* manufacturing floor.
*
* Default to root-only access to the data; EEPROMs often hold data
* that's sensitive for read and/or write, like ethernet addresses,
* security codes, board-specific manufacturing calibrations, etc.
*/
/*通过sysfs文件系统导出EEPROM的字节,因为这是很方便的.也许其他内核代码也是这样做的:比如保存板子的以太网地址,或者是生产商的特定板的校验数据.
默认只有root用户能够访问的数据.EEPROMs经常保存一些敏感的读或写的数据,像是以太网地址,安全码,特定板的校准数据等*/
sysfs_bin_attr_init(&at25->bin);/*初始化一个动态分配的bin_attribute属性*/
at25->bin.attr.name = "eeprom";/*属性的名字*/
at25->bin.attr.mode = S_IRUSR;/*属性的模式(用户可读)*/
at25->bin.read = at25_bin_read;/*属性的读方法*/
at25->mem.read = at25_mem_read;/*片子的内存读函数*/ at25->bin.size = at25->chip.byte_len;
if (!(chip.flags & EE_READONLY)) {/*flags用来标志eeprom的位宽和读写模式,具体到dm365平台此flags为EE_ADDR2*/
at25->bin.write = at25_bin_write;/*如果eeprom片子不是只读的话,那么就设置属性的写方法*/
at25->bin.attr.mode |= S_IWUSR;/*增加属性的模式(用户可写)*/
at25->mem.write = at25_mem_write;/*片子的内存写函数*/
} err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);/*创建一个二进制的属性文件*/
if (err)
goto fail; if (chip.setup)/*如果片子定义了setup函数,具体到dm365平台,此platform_data就是arch/arm/mach-davinci/board-ddm365-evm.c中定义的的at25640里并没有
定义这个函数,因此为空*/
chip.setup(&at25->mem, chip.context);/*使用片子的setup函数做一些初始化*/ dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
(at25->bin.size < )
? at25->bin.size
: (at25->bin.size / ),
(at25->bin.size < ) ? "Byte" : "KByte",
at25->chip.name,
(chip.flags & EE_READONLY) ? " (readonly)" : "",
at25->chip.page_size);
return ;
fail:
dev_dbg(&spi->dev, "probe err %d\n", err);
kfree(at25);
return err;
}
根据代码就能知道,这个spi_device实际上对应的是一个eeprom,而这里就是它的操作的一些初始化.也就是说,对于分析的这个davinci代码的实例,是dm365平台的,其开发板上应该是将eeprom通过spi总线挂接在了spi_master上.也就是挂接在了SOC上,因为我们的dm365片子本身就集成了spi_master.之后要访问eeprom其实回调最终的都是这里提供的一些接口.听起来有点黏糊,看个图吧:
哎,就写到这里,本人不会用visio,画个这图画了一下午~丢人啊!
SPI子系统分析之四:驱动模块的更多相关文章
- SPI子系统分析之三:驱动模块
内核版本:3.9.5 SPI核心层(平台无关) SPI子系统初始化的第一步就是将SPI总线注册进内核,并且在/sys下创建一个spi_master的类,以后注册的从设备都将挂接在该总线下. 下列函数位 ...
- SPI子系统分析之一:框架
内核版本:3.9.5 SPI子系统概述: 一个SPI主控制器对应一条SPI总线,当然在系统中有唯一的总线编号. SPI总线上有两类设备: 其一是主控端,通常作为SOC系统的一个子模块出现,很多嵌入式M ...
- SPI子系统分析之二:数据结构【转】
转自:http://www.cnblogs.com/jason-lu/articles/3164901.html 内核版本:3.9.5 spi_master struct spi_master用来描述 ...
- SPI子系统分析之二:数据结构
内核版本:3.9.5 spi_master struct spi_master用来描述一个SPI主控制器,我们一般不需要自己编写spi控制器驱动. /*结构体master代表一个SPI接口,或者叫一个 ...
- input子系统分析之三:驱动模块
内核版本:3.9.5 本节将以even handler来分析设备的注册和打开的过程,分析之前不妨回顾一下上节介绍的数据结构. 结合前两节分析可知,input子系统分为3层,最上一层是event han ...
- [国嵌攻略][159][SPI子系统]
SPI 子系统架构 1.SPI core核心:用于连接SPI客户驱动和SPI主控制器驱动,并且提供了对应的注册和注销的接口. 2.SPI controller driver主控制器驱动:用来驱动SPI ...
- SPI子系统
一.SPI子系统模型 三个组成部分: SPI核心:连通了SPI客户驱动.SPI主控制器驱动 SPI控制器驱动:驱动芯片中的SPI控制器 SPI的FLASH(客户驱动) 二.SPI控制器驱动分析 sta ...
- spi子系统之驱动SSD1306 OLED
spi子系统之驱动SSD1306 OLED 接触Linux之前,曾以为读源码可以更快的学习软件,于是前几个博客都是一边读源码一边添加注释,甚至精读到每一行代码,实际上效果并不理想,看过之后就忘记了.主 ...
- Android多线程分析之四:MessageQueue的实现
Android多线程分析之四:MessageQueue的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前面两篇文章<Androi ...
随机推荐
- hadoop 的job.setOutputKeyClass和job.setOutputValueClass的几个问题
昨天写了一个mapreduce函数一直有错误,找不到错误,今天找了一天终于解决了,原来是hadoop 的job.setOutputKeyClass和job.setOutputValueClas设置输出 ...
- For input string: "null"
java.lang.NumberFormatException: For input string: "null" 在开发中你是否遇到过这样的问题,不管请求到的值是什么都能进入不为 ...
- Mac OS安装php-redis扩展
下载php-redis(用于php5.x的版本),地址:https://nodeload.github.com/nicolasff/phpredis/zip/master. 如果是php7.2,选择p ...
- Tornador之初识(一)
一.最简单的web服务器 import socket def handle_request(client): buf = client.recv(1024) client.send("HTT ...
- 【转】VC中MessageBox与AfxMessageBox用法与区别
原文网址:http://blog.csdn.net/holybin/article/details/28403109 一.MessageBox()用法 1.函数原型 Messagebox函数在Win3 ...
- redis+php实现微博功能(三)
个人主页显示微博列表(自己及关注人的微博列表) /*获取最新的50微博信息列表,列出自己发布的微博及我关注用户的微博 *1.根据推送的信息获取postid *2.根据postid获取发送的信息 */ ...
- xshell 提示 继续使用此程序必须应用到最新的更新或使用新版本 的解决方案
当打开正在使用的xshell后,提示“继续使用此程序必须应用到最新的更新或使用新版本 ” 是因为我们正在使用的是xshell5 版本,需要我们再安装一个xshell6 版本 我个人使用的是家庭/教育 ...
- PHP使用RabbitMQ
基本概念 Broker:简单来说就是消息队列服务器实体. Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列. Queue:消息队列载体,每个消息都会被投入到一个或多个队列. Bind ...
- c#QQ邮件编程学习(收发邮件)
本次c#实现邮件管理编程的目的是实现第三方邮件管理,邮箱基于QQ邮箱,发送邮件直接采用.NET自带的System.Net.Mail类,接收邮件采用第三方组件Lumisoft.Net.现将基本实现的接收 ...
- Java中的<< 和 >> 和 >>> 详细分析
<<表示左移移,不分正负数,低位补0: 注:以下数据类型默认为byte-8位 左移时不管正负,低位补0 正数:r = 20 << 2 20的二进制补码:0001 0100 向左 ...