着两天弄了个OLED,想在tiny4412开发板上调试一下,顺便熟悉一下Linux内核下SPI驱动框架。
内核为3.0.8,先上代码,后面分析框架。
内核中涉及到的文件主要文件有:
linux-3.0.86/arch/arm/mach-exynos/mach-tiny4412.c 内核中板级配置文件,较新的内核都采用设备树的方法了。
linux-3.0.86/arch/arm/mach-exynos/dev-spi.c spi底层配置,如管脚功能配置,时钟初始化等
linux-3.0.86/drivers/spi/spi.c spi中间层
linux-3.0.86/drivers/spi/spi_s3c64xx.c 针对exyons tiny4412这颗芯片的SPI底层驱动,提供底层收发函数。
驱动程序:spi_oled_drv.c
先看一下mach-tiny4412.c,在本文件中初始化一个spi_board_info结构体。
static struct s3c64xx_spi_csinfo spi0_csi[] = {
[0] = {
.line = EXYNOS4_GPB(1), //指定SPI的片选引脚
.set_level = gpio_set_value,
.fb_delay = 0x2,
},
};
static struct spi_board_info spi0_board_info[] __initdata = {
#if 0
{
.modalias = "spidev", //这个设备是本文件中自带的,同时内核中
.platform_data = NULL, //drivers/spi/spidev.c中定义了同名的
.max_speed_hz = 10*1000*1000, //spi_driver这两者匹配后会在/dev/下创建
.bus_num = 0, //spidev.x.x设备。然后等于spi驱动已经可以
.chip_select = 0, //用了,应用程序直接访问/dev/spidev.0.0等
.mode = SPI_MODE_0, //设备就可以直接使用。
.controller_data = &spi0_csi[0],
},
#endif
#if 1 //我们不使用内核自带的,我们自己实现驱动。
{
.modalias = "spi_oled", //定义spi设备名字,需要与驱动中的name一样,
.platform_data = (const void*)EXYNOS4_GPX1(0), //指定oled的dc引脚
.max_speed_hz = 10*1000*1000, //spi设备最大频率
.bus_num = 0, //指定本spi设备挂在哪个spi 上,挂在spi0
.chip_select = 0, //指定片选引脚是在spi0_csi数组中哪个,因为同一个spi总线下
//可挂多个从设备。多个从设备的片选引脚定义在spi0_csi中。
.mode = SPI_MODE_0,
.controller_data = &spi0_csi[0],
}
#endif
};
接下来是对spi0 的初始化
sclk = clk_get(spi0_dev, "dout_spi0");
if (IS_ERR(sclk))
dev_err(spi0_dev, "failed to get sclk for SPI-0\n");
prnt = clk_get(spi0_dev, "mout_mpll_user");
if (IS_ERR(prnt))
dev_err(spi0_dev, "failed to get prnt\n");
if (clk_set_parent(sclk, prnt))
printk(KERN_ERR "Unable to set parent %s of clock %s.\n",
prnt->name, sclk->name);
clk_set_rate(sclk, 800 * 1000 * 1000);
clk_put(sclk);
clk_put(prnt);
if (!gpio_request(EXYNOS4_GPB(1), "SPI_CS0")) { //初始化片选引脚,设置为output
gpio_direction_output(EXYNOS4_GPB(1), 1);
s3c_gpio_cfgpin(EXYNOS4_GPB(1), S3C_GPIO_SFN(1));
s3c_gpio_setpull(EXYNOS4_GPB(1), S3C_GPIO_PULL_UP);
exynos_spi_set_info(0, EXYNOS_SPI_SRCCLK_SCLK,
ARRAY_SIZE(spi0_csi));
}
spi_register_board_info(spi0_board_info, ARRAY_SIZE(spi0_board_info)); //注册i2c设备。
重点看一下spi_register_board_info,最终会内核会根据spi0_board_info构造一个spi_device并最终添加到内核中去。
spi_register_board_info(spi0_board_info, ARRAY_SIZE(spi0_board_info));
list_add_tail(&bi->list, &board_list);
list_for_each_entry(master, &spi_master_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
dev = spi_new_device(master, bi); //创建spi_device
status = spi_add_device(proxy);
//if (spi->chip_select >= spi->master->num_chipselect) {
if (spi->chip_select >= 0xffff) {//这里的num_chipselect为1
太小了,只默认一个spi挂一个设备,我们将其改为0xff,保证这里一定能成功
dev_err(dev, "cs%d >= max %d\n",
spi->chip_select,
spi->master->num_chipselect);
return -EINVAL;
}
bus_find_device_by_name(&spi_bus_type, NULL, dev_name(&spi->dev));
status = device_add(&spi->dev); //到这里就又回到了设备驱动模型中。
内核会根据设备的名字跟驱动中的名字进行匹配,当匹配上之后,驱动中的probe
函数就会调用。
看一下驱动中是如何处理的:
static ssize_t oled_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
if(count > 4096)
return -EINVAL;
ret = copy_from_user(ker_buf, buf, count);
OLED_Set_DC(1); /*Data*/
printk("spi_write \n");
ret = spi_write(spi_oled_dev, ker_buf, count);
if (ret) {
printk("spi_write err\n");
}
return 0;
}
static struct file_operations oled_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = oled_ioctl,
.write = oled_write,
};
static int __devinit spi_oled_probe(struct spi_device *spi)
{
spi_oled_dev = spi;
printk("spi oled probe...");
//s3c2410_gpio_cfgpin(spi_oled_dc_pin, S3C2410_GPIO_OUTPUT);
#if 1
spi_oled_dc_pin = (int)spi->dev.platform_data;
if (!gpio_request(spi_oled_dc_pin, "oled_dc")) {
gpio_direction_output(spi_oled_dc_pin, 1);
printk("spi_oled_dc_pin request and set ok\n ");
}
#endif
if (!gpio_request(spi_oled_res_pin, "oled_res")) {
gpio_direction_output(spi_oled_res_pin, 1);
printk("spi_oled_res_pin request and set ok\n ");
}
printk("kmalloc \n ");
ker_buf = kmalloc(4096, GFP_KERNEL);
major = register_chrdev(0, "spi_oeld", &oled_ops);
class = class_create(THIS_MODULE, "spi_oled");
device_create(class, NULL, MKDEV(major, 0), NULL, "spi_oled");
printk("probee end \n ");
return 0;
}
static struct spi_driver spi_oled_drv = {
.driver = {
.name = "spi_oled",
.owner = THIS_MODULE,
},
.probe = spi_oled_probe,
.remove = __devexit_p(spi_oled_remove),
};
static int spi_oled_init(void)
{
return spi_register_driver(&spi_oled_drv);
}
static void spi_oled_exit(void)
{
spi_unregister_driver(&spi_oled_drv);
}
module_init(spi_oled_init);
module_exit(spi_oled_exit);
驱动函数中也很简单,就分配一个 struct spi_driver结构体,然后设置,最后调用spi_register_driver(&spi_oled_drv);注册到内核中,当内核中的设备驱动模型,匹配相同名字的设备后,probe函数就被调用,在probe函数中想干什么就干什么。
我们在probe函数中,注册了一个字符设备驱动,并创建/dev/spi_oled 这样应用程序就能通过/dev/spi_oled 访问驱动。
在字符设备的write函数中我们直接调用spi_write函数,写数据给你spi。
那么着spi_write最后是怎么把数据发送出去的,最后又是怎么调用到spi控制器硬件发送数据呢?
根据我们的猜测,重点是就是linux-3.0.86/drivers/spi/spi.c 中
spi.c向上为spi_write函数提供了统一的接口,供其调用,这样驱动程序也能很好的移植到被的平台上。
spi.c向下为各个厂家的底层spi驱动提供统一的注册接口,这样每个厂家可以调用这个接口把自己家的底层驱动注册进去内核供上层使用。
我们先看spi_write函数。
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
初始化message 并把message放入链表,调用spi_sync
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
return __spi_sync(spi, message, 0);
}
static int __spi_sync(struct spi_device *spi, struct spi_message *message,
int bus_locked)
{
...
status = spi_async_locked(spi, message);
...
}
int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{
...
ret = __spi_async(spi, message);
...
}
static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;
/* Half-duplex links include original MicroWire, and ones with
* only one data pin like SPI_3WIRE (switches direction) or where
* either MOSI or MISO is missing. They can also be caused by
* software limitations.
*/
if ((master->flags & SPI_MASTER_HALF_DUPLEX)
|| (spi->mode & SPI_3WIRE)) {
struct spi_transfer *xfer;
unsigned flags = master->flags;
list_for_each_entry(xfer, &message->transfers, transfer_list) {
if (xfer->rx_buf && xfer->tx_buf)
return -EINVAL;
if ((flags & SPI_MASTER_NO_TX) && xfer->tx_buf)
return -EINVAL;
if ((flags & SPI_MASTER_NO_RX) && xfer->rx_buf)
return -EINVAL;
}
}
message->spi = spi;
message->status = -EINPROGRESS;
return master->transfer(spi, message); //最后调用到了master的transfer函数去发送message。
}
那么master->transfer是哪里注册的呢?
tiny4412底层spi驱动是drivers/spi/spi_s3c64xx.c
我们分析一下这个文件。
static int __init s3c64xx_spi_init(void)
{
return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
module_init(s3c64xx_spi_init); 注册一个paltform driver 并直接调用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;
dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); //获取各种资源。
if (dmatx_res == NULL) {
dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n");
return -ENXIO;
}
......
master = spi_alloc_master(&pdev->dev, //分配一个spi_master
sizeof(struct s3c64xx_spi_driver_data));
if (master == NULL) {
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, master);
sdd = spi_master_get_devdata(master);
sdd->master = master; //设置master
sdd->cntrlr_info = sci;
sdd->pdev = pdev;
sdd->sfr_start = mem_res->start;
sdd->tx_dmach = dmatx_res->start;
sdd->rx_dmach = dmarx_res->start;
sdd->cur_bpw = 8;
master->bus_num = pdev->id;
master->setup = s3c64xx_spi_setup; //注册master setup函数
master->transfer = s3c64xx_spi_transfer; //注册master transfer函数
master->num_chipselect = sci->num_cs;
master->dma_alignment = 8;
/* 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");
if (IS_ERR(sdd->clk)) {
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
ret = PTR_ERR(sdd->clk);
goto err3;
}
..... clk相关的初始化工作。
sdd->workqueue = create_singlethread_workqueue(
dev_name(master->dev.parent));
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);
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;
}
sdd->rx_dmach, sdd->tx_dmach);
/* Disable the clock */
clk_disable(sdd->src_clk);
clk_disable(sdd->clk);
return ret;
}
因此这里就看到master->transfer一般是芯片厂家来实现,并注册进内核中去。
至此,SPI框架已经分析完。