linux内核提供的源码
/* linux/drivers/spi/spi_s3c24xx.c * * Copyright (c) 2006 Ben Dooks * Copyright (c) 2006 Simtec Electronics * Ben Dooks <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #include <linux/init.h> #include <linux/spinlock.h> #include <linux/workqueue.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/err.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> #include <linux/spi/spi_bitbang.h> #include <asm/io.h> #include <asm/dma.h> #include <asm/hardware.h> #include <asm/arch/regs-gpio.h> #include <asm/plat-s3c24xx/regs-spi.h> #include <asm/arch/spi.h> struct s3c24xx_spi { /* bitbang has to be first */ struct spi_bitbang bitbang; struct completion done; void __iomem *regs; int irq; int len; int count; void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); /* data buffers */ const unsigned char *tx; unsigned char *rx; struct clk *clk; struct resource *ioarea; struct spi_master *master; struct spi_device *curdev; struct device *dev; struct s3c2410_spi_info *pdata; }; #define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT) #define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP) static inline struct s3c24xx_spi *to_hw(struct spi_device *sdev) { return spi_master_get_devdata(sdev->master); } static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol) { s3c2410_gpio_setpin(spi->pin_cs, pol); } static void s3c24xx_spi_chipsel(struct spi_device *spi, int value) { struct s3c24xx_spi *hw = to_hw(spi); unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; unsigned int spcon; switch (value) { case BITBANG_CS_INACTIVE:/*CS无效时*/ hw->set_cs(hw->pdata, spi->chip_select, cspol^1); /*即调用s3c24xx_spi_gpiocs使CS无效*/ break; case BITBANG_CS_ACTIVE: /*CS有效时*/ spcon = readb(hw->regs + S3C2410_SPCON);/*获取目前SPCON寄存器的值*/ /*开始设置工作模式*/ if (spi->mode & SPI_CPHA) spcon |= S3C2410_SPCON_CPHA_FMTB; else spcon &= ~S3C2410_SPCON_CPHA_FMTB; if (spi->mode & SPI_CPOL) spcon |= S3C2410_SPCON_CPOL_HIGH; else spcon &= ~S3C2410_SPCON_CPOL_HIGH spcon |= S3C2410_SPCON_ENSCK;/*激活时钟sck输出*/ /* write new configration */ writeb(spcon, hw->regs + S3C2410_SPCON);/*保存新的配置*/ hw->set_cs(hw->pdata, spi->chip_select, cspol);/*即调用s3c24xx_spi_gpiocs使CS有效*/ break; } } static int s3c24xx_spi_setupxfer(struct spi_device *spi,struct spi_transfer *t) { struct s3c24xx_spi *hw = to_hw(spi); unsigned int bpw; unsigned int hz; unsigned int div; /*没有transfer,则使用spi_device进行配置*/ bpw = t ? t->bits_per_word : spi->bits_per_word; hz = t ? t->speed_hz : spi->max_speed_hz; if (bpw != 8) { dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw); return -EINVAL; } div = clk_get_rate(hw->clk) / hz; /* is clk = pclk / (2 * (pre+1)), or is it * clk = (pclk * 2) / ( pre + 1) *//*计算预分频系数*/ div = (div / 2) - 1; if (div < 0) div = 1; if (div > 255) div = 255; dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz); writeb(div, hw->regs + S3C2410_SPPRE);/*设置预分频系数*/ spin_lock(&hw->bitbang.lock); if (!hw->bitbang.busy) { hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);/*即调用s3c24xx_spi_chipsel使CS无效*/ /* need to ndelay for 0.5 clocktick ? */ } spin_unlock(&hw->bitbang.lock); return 0; } /* the spi->mode bits understood by this driver: */ #define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH) static int s3c24xx_spi_setup(struct spi_device *spi)/*maser.setup方法*/ { int ret; if (!spi->bits_per_word)/*没有设置则使用8位*/ spi->bits_per_word = 8; if (spi->mode & ~MODEBITS) {/*检查mode是否有错*/ dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", spi->mode & ~MODEBITS); return -EINVAL; } ret = s3c24xx_spi_setupxfer(spi, NULL); if (ret < 0) { dev_err(&spi->dev, "setupxfer returned %d\n", ret); return ret; } dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", __FUNCTION__, spi->mode, spi->bits_per_word, spi->max_speed_hz); return 0; } static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count) { return hw->tx ? hw->tx[count] : 0;/*发送缓冲区指针是否为空,空则发送0*/ } static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)/*bitbang.txrx_bufs方法*/ { struct s3c24xx_spi *hw = to_hw(spi); dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", t->tx_buf, t->rx_buf, t->len); /*保存transfer相关数据到s3c24xx_sp结构中*/ hw->tx = t->tx_buf; hw->rx = t->rx_buf; hw->len = t->len; hw->count = 0; /* send the first byte *//*发送第一个数据,tx[0]*/ writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); wait_for_completion(&hw->done); return hw->count;/*返回发送的字节数*/ } static irqreturn_t s3c24xx_spi_irq(int irq, void *dev) { struct s3c24xx_spi *hw = dev; unsigned int spsta = readb(hw->regs + S3C2410_SPSTA); unsigned int count = hw->count; if (spsta & S3C2410_SPSTA_DCOL) { dev_dbg(hw->dev, "data-collision\n"); complete(&hw->done); goto irq_done; } if (!(spsta & S3C2410_SPSTA_READY)) { dev_dbg(hw->dev, "spi not ready for tx?\n"); complete(&hw->done); goto irq_done; } hw->count++; if (hw->rx) hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT); count++; if (count < hw->len) writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT); else complete(&hw->done); irq_done: return IRQ_HANDLED; } static int __init s3c24xx_spi_probe(struct platform_device *pdev) { struct s3c24xx_spi *hw; struct spi_master *master; struct spi_board_info *bi; struct resource *res; int err = 0; int i; /*分配master结构体,其中包括s3c24xx_spi结构的内存空间,使用master.dev.driver_data指向它*/ master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); if (master == NULL) { dev_err(&pdev->dev, "No memory for spi_master\n"); err = -ENOMEM; goto err_nomem; } /*获得s3c24xx_spi结构,并清0该结构*/ hw = spi_master_get_devdata(master); memset(hw, 0, sizeof(struct s3c24xx_spi)); hw->master = spi_master_get(master);/*保存master结构体,同时增加引用计数*/ hw->pdata = pdev->dev.platform_data;/*获取s3c2410_spi_info结构体指针*/ hw->dev = &pdev->dev; /*保存platform设备的dev*/ if (hw->pdata == NULL) { dev_err(&pdev->dev, "No platform data supplied\n"); err = -ENOENT; goto err_no_pdata; } platform_set_drvdata(pdev, hw);/*让platform_device.dev.driver_data 指向 s3c24xx_spi*/ init_completion(&hw->done);/*初始化completion*/ /* setup the state for the bitbang driver *//*填充bitbang字段*/ hw->bitbang.master = hw->master; hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;/*在spi_bitbang_start被调用*/ hw->bitbang.chipselect = s3c24xx_spi_chipsel; hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; hw->bitbang.master->setup = s3c24xx_spi_setup; dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang); /* find and map our resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); err = -ENOENT; goto err_no_iores; } hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1, pdev->name); if (hw->ioarea == NULL) { dev_err(&pdev->dev, "Cannot reserve region\n"); err = -ENXIO; goto err_no_iores; } hw->regs = ioremap(res->start, (res->end - res->start)+1); if (hw->regs == NULL) { dev_err(&pdev->dev, "Cannot map IO\n"); err = -ENXIO; goto err_no_iomap; } hw->irq = platform_get_irq(pdev, 0); if (hw->irq < 0) { dev_err(&pdev->dev, "No IRQ specified\n"); err = -ENOENT; goto err_no_irq; } err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); if (err) { dev_err(&pdev->dev, "Cannot claim IRQ\n"); goto err_no_irq; } hw->clk = clk_get(&pdev->dev, "spi"); if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device\n"); err = PTR_ERR(hw->clk); goto err_no_clk; } /* for the moment, permanently enable the clock */ clk_enable(hw->clk); /* program defaults into the registers *//*spi控制器初始化*/ writeb(0xff, hw->regs + S3C2410_SPPRE); /*设置预分频系数,baudrate=pclk/2/(prescaler value+1)*/ writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);/*使能master out keep*/ writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);/*master, interrupt mode*/ /* setup any gpio we can */ if (!hw->pdata->set_cs) { hw->set_cs = s3c24xx_spi_gpiocs;/*给出分配cs管脚函数*/ s3c2410_gpio_setpin(hw->pdata->pin_cs, 1); s3c2410_gpio_cfgpin(hw->pdata->pin_cs, S3C2410_GPIO_OUTPUT); } else hw->set_cs = hw->pdata->set_cs; /* register our spi controller */ err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } /* register all the devices associated */ bi = &hw->pdata->board_info[0]; for (i = 0; i < hw->pdata->board_size; i++, bi++) { dev_info(hw->dev, "registering %s\n", bi->modalias); bi->controller_data = hw; spi_new_device(master, bi); } return 0; err_register: clk_disable(hw->clk); clk_put(hw->clk); err_no_clk: free_irq(hw->irq, hw); err_no_irq: iounmap(hw->regs); err_no_iomap: release_resource(hw->ioarea); kfree(hw->ioarea); err_no_iores: err_no_pdata: spi_master_put(hw->master);; err_nomem: return err; } static int __exit s3c24xx_spi_remove(struct platform_device *dev) { struct s3c24xx_spi *hw = platform_get_drvdata(dev); platform_set_drvdata(dev, NULL); spi_unregister_master(hw->master); clk_disable(hw->clk); clk_put(hw->clk); free_irq(hw->irq, hw); iounmap(hw->regs); release_resource(hw->ioarea); kfree(hw->ioarea); spi_master_put(hw->master); return 0; } #ifdef CONFIG_PM static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg) { struct s3c24xx_spi *hw = platform_get_drvdata(pdev); clk_disable(hw->clk); return 0; } static int s3c24xx_spi_resume(struct platform_device *pdev) { struct s3c24xx_spi *hw = platform_get_drvdata(pdev); clk_enable(hw->clk); return 0; } #else #define s3c24xx_spi_suspend NULL #define s3c24xx_spi_resume NULL #endif MODULE_ALIAS("s3c2410_spi"); /* for platform bus hotplug */ static struct platform_driver s3c24xx_spidrv = { .remove = __exit_p(s3c24xx_spi_remove), .suspend = s3c24xx_spi_suspend, .resume = s3c24xx_spi_resume, .driver = { .name = "s3c2410-spi", .owner = THIS_MODULE, }, }; static int __init s3c24xx_spi_init(void) { return platform_driver_probe(&s3c24xx_spidrv, s3c24xx_spi_probe); } static void __exit s3c24xx_spi_exit(void) { platform_driver_unregister(&s3c24xx_spidrv); } module_init(s3c24xx_spi_init); module_exit(s3c24xx_spi_exit); MODULE_DESCRIPTION("S3C24XX SPI Driver"); MODULE_AUTHOR("Ben Dooks, <[email protected]>"); MODULE_LICENSE("GPL");
驱动分析
主要的数据结构有:struct platform_driver
驱动的入口,出口
s3c24xx_spi_init:使用的是platform_driver_probe(&s3c24xx_spidrv, s3c24xx_spi_probe)将其注册到平台设备总线上去。因为设备不可热插拔,所以使用该函数,而不是platform_driver_register。调用了platform_driver_probe注册platform驱动,注册完成以后将会调用platform的s3c24xx_spi_probe函数。
s3c24xx_spi_exit:platform_driver_unregister(&s3c24xx_spidrv)
探测函数的关键函数分析
spi_bitbang_start。这个函数位于drivers/spi/spi_bitbang.c。
在这里,定义了控制器的transfer方法为spi_bitbang_transfer。创建了一个工作队列和一个工作bitbang_work,同时创建了一个链表。最后,调用了spi_register_master函数,该函数将完成SPI控制器的注册,其中还牵涉到spi_device的注册。
spi_register_master位于drivers/spi/spi_bitbang.c。在spi_register_master函数中,执行了相关的检查,然后注册了master设备,随后调用了scan_boardinfo,位于drivers/spi/spi.c。这个函数通过boardinfo遍历的spi_board_info数组,而spi_board_info是在内核初始化过程中由spi_register_board_info进行注册的,在平台资源中做了这个步骤,向内核注册。
S3C2440共有两个接口:spi0和spi1。chip->bus_num表示该设备使用哪个spi接口,而master->bus_num正好表示了当前的接口。该函数中,遍历spi_board_info,通过bus_num完成SPI设备和SPI控制器的匹配,匹配成功则开始建立spi_device设备,该过程通过调用spi_new_device实现,位于drivers/spi/spi.c。关于核心层的分析见下一节。
在注册spi_device之前,调用了master的setup方法,将调用s3c24xx_spi_setup,该方法又将调用s3c24xx_spi_setupxfer和s3c24xx_spi_chipsel函数。s3c24xx_spi_setupxfer函数计算预分频系数并写入寄存器。s3c24xx_spi_chipsel函数用于禁止或使能CS信号。当使能CS信号时,要设置控制寄存器。这里调用是禁止CS信号。在探测函数中由spi_bitbang_start调用所引起的一系列函数调用如下图所示。
在s3c24xx_spi_txrx函数中,首先发送了待发送数据中的第一个字节,随后就调用wait_for_completion来等待剩余的数据发送完成。这里的completion是master驱动层的,spi设备驱动也有一个completion,用于IO同步,不要混淆。当第一个数据发送完成以后,SPI中断产生,开始执行中断服务程序。在中断服务程序中,将判断是否需要读取数据,如果是则从寄存器中读取数据。如果是使用read系统调用,那么在此发送的数据将是0。随后发送下一个数据,直到数据发送完成。发送完成后调用complete,使在s3c24xx_spi_txrx的wait_for_completion得以返回。接着,s3c24xx_spi_txrx就将返回已发送的字节数。
int spi_bitbang_start(struct spi_bitbang *bitbang) { int status; if (!bitbang->master || !bitbang->chipselect) return -EINVAL; INIT_WORK(&bitbang->work, bitbang_work); /*初始化工作,工作为bitbang_work*/ spin_lock_init(&bitbang->lock); /*初始化自旋锁*/ INIT_LIST_HEAD(&bitbang->queue); /*初始化链表头,链表为双向循环链表*/ if (!bitbang->master->transfer) /*master的transfer方法没有定义过*/ bitbang->master->transfer = spi_bitbang_transfer; /*使用spi_bitbang_transfe方法*/ if (!bitbang->txrx_bufs) { /*如果bitbang没有txrx_bufs方法,在probe函数中定义过该方法*/ bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; if (!bitbang->master->setup) { if (!bitbang->setup_transfer) bitbang->setup_transfer = spi_bitbang_setup_transfer; bitbang->master->setup = spi_bitbang_setup; bitbang->master->cleanup = spi_bitbang_cleanup; } } else if (!bitbang->master->setup) /*setup方法在probe函数中有定义*/ return -EINVAL; /* this task is the only thing to touch the SPI bits */ bitbang->busy = 0; bitbang->workqueue = create_singlethread_workqueue( /*创建工作队列*/ dev_name(bitbang->master->dev.parent)); 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(bitbang->master); /*注册spi控制器*/ if (status < 0) goto err2; return status; err2: destroy_workqueue(bitbang->workqueue); err1: return status; } EXPORT_SYMBOL_GPL(spi_bitbang_start);
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); status = device_add(&master->dev); /*注册设备*/ 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); status = 0; done: return status; } EXPORT_SYMBOL_GPL(spi_register_master);