由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件架构的思想,感觉linux SPI驱动很强大,水很深,废话少说,SPI总线上有两类设备:一类是主机端,通常作为SOC系统的一个子模块出现,比如很多嵌入式MPU中都常常包含SPI模块。一类是从机被控端,例如一些SPI接口的Flash、传感器等等。主机端是SPI总线的控制者,通过使用SPI协议主动发起SPI总线上的会话。而受控端则被动接受SPI主控端的指令,并作出响应的响应,本文读者前提是必须熟练掌握linux Platform总线驱动模型 和基本字符设备驱动的实现

  SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。SPI常用四种数据传输模式,主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。同I2C子系统类似,SPI子系统分为3个部分,分别是SPI核心层、主控制器驱动和协议驱动,通俗一点就是SPI核心层主要完成1.定义并注册SPI总线spi_bus_type和控制器类spi_master_class;2.提供spi_driver,spi_device和spi_master的分配,创建,注册和注销;3.实现SPI通信方法的上层代码。主控制器驱动对应I2C的适配器驱动,SPI用spi_master来描述相应的控制器,通常用spi_bitbang来控制实际的数据传输,功能非常类似与i2c_algorithm。协议驱动类比I2C设备驱动,可以理解成客户端驱动,下面详细分析和实现基于linux3.14.78内核版本 SPI在S3C2440/6410上驱动移植.

  Step1,实现SPI控制器的设备接口(相对应有两种方式,一种实现方式是S3c2440纯粹通过配置来实现,另一种实现方式针对m25p10,独立编写单独的spi驱动模块)

  首先针对S3C2440,SPI控制器的设备接口在drivers/spi/spidev.c中实现,下图为相应的软件流程图,spidev.c中的spidev_init()作为模块初始化函数,在系统启动或者模块加载是被调用,主要完成以下三种操作:

  1、调用register_chrdev()为SPI控制器注册主设备号为153,次设备号范围为0~255,文件操作集合为spidev_fops的字符设备;

  2、调用class_create()注册一个名为“spidev”的设备类;

  3、调用spi_register_driver()向系统添加SPI控制器的设备驱动spidev_spi;

  spi_register_driver()将驱动spidev_spi添加到SPI核心层注册的spi_bus_type总线上,注意,该总线属于spi总线,Spi总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义,对应的匹配规则是(高版本中的匹配规则会稍有变化,引入了id_table,可以匹配多个spi设备名称)详见以下代码。由于spi总线的匹配方式是检查spi_device.modalias与spi_driver.driver.name是否相同,而spidev_spi的driver.name是“spidev”,so 只有spi_bus_type总线上的modalias为“spidev”的设备,才可以与spidev_spi驱动匹配。由于S3C2440拥有两个SPI控制器,对应的平台信息是s3c_device_spi0和s3c_device_spi1,可以将其添加到机器配置文件的My2440_devices数组中,驱动中probe方法中用到的总线编号、片选总数等信息来源于平台数据,这些数据需要用户添加,添加方式有两种,平台设备的平台数据类型是S3C2410_SPI_info,在板级初始化文件中可以为两个SPI平台设备分别定义和添加这些平台数据,此外为了支持SPI控制器设备接口功能,还需要在机器配置文件为SPI控制器设备添加并注册spi_board_info对象,详见下面代码。

  设备与驱动匹配后,通过调用spidev_spi的probe方法来绑定工作见代码。SPI控制器设备操作集合是spidev_fops,主要包括spidev_write,spidev_read,spidev_ioctl,spidev_open和spidev_release。下面的代码注释中详尽分析了spidev_read()函数的实现过程,spidev_write()的分析和实现方法与read类似,spidev_open()会遍历device_list链表,找出其中设备号与打开设备文件的inode.i_rdev相等的spidev_data对象,并将该对象记录在文件的私有数据filp->private_data中,以供read和write函数获取,此外要实现全双工的传输需要借助控制器设备的ioctl方法,对应实现函数是spidev_ioctl(),全双工传输也是通过spidev_sync()完成的,与半双工传输唯一不同的是在消息的传输段中,tx_buffer和rx_buffer同时被设置,它还提供获取和修改时钟模式、子宽、最大时钟频率属性的命令。

 1 static struct s3c2410_spi_info s3c2410_spi0_platdata = {  2     .pin_cs = S3C2410_GPG2,  3     .num_cs = 2,  4     .bus_num = 0,  5     .gpio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13,  6 };  7 static struct spi_board_info s3c2410_spi0_board[] =  8 {  9     [0] = {
10         .modalias = "spidev",
11         .bus_num = 0,
12         .chip_select = 0,
13         .max_speed_hz = 500 * 1000,
14     },
15     [1] = {
16         .modalias = "at25",
17         .platform_data = &at25_eeprom_data,
18         .bus_num = 0,
19         .chip_select = 1,
20         .max_speed_hz = 500 * 1000,
21     }
22 };
23 static struct s3c2410_spi_info s3c2410_spi1_platdata = {
24     .pin_cs = S3C2410_GPG3,
25     .num_cs = 1,
26     .bus_num = 1,
27     .gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,
28 };
29 static struct spi_board_info s3c2410_spi1_board[] =
30 {
31     [0] = {
32         .modalias = "spidev",
33         .bus_num = 1,
34         .chip_select = 0,
35         .max_speed_hz = 500 * 1000,
36     }
37 };
38 static void __init My2440_machine_init(void)
39 {
40 s3c24xx_fb_set_platdata(&My2440_fb_info);
41 s3c_i2c0_set_platdata(NULL);
42 i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices));
43 s3c_device_spi0.dev.platform_data= &s3c2410_spi0_platdata;
44 spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));
45 s3c_device_spi1.dev.platform_data= &s3c2410_spi1_platdata;
46 spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));
47 s3c_device_nand.dev.platform_data = &My2440_nand_info;
48 s3c_device_sdi.dev.platform_data = &My2440_mmc_cfg;
49 platform_add_devices(My2440_devices, ARRAY_SIZE(My2440_devices));
50 }   

1 struct bus_type spi_bus_type = {
2                 .name = "spi",
3                 .dev_attrs = spi_dev_attrs,
4                 .match = spi_match_device,
5                 .uevent = spi_uevent,
6                 .suspend = spi_suspend,
7                 .resume = spi_resume,
8         };

 static int spi_match_device(struct device *dev, struct device_driver *drv){const struct spi_device *spi = to_spi_device(dev);return strcmp(spi->modalias, drv->name) == 0;}

static int spidev_probe(struct spi_device *spi)
{struct spidev_data    *spidev;int            status;unsigned long        minor;/* Allocate driver data, 分配并初始化一个spidev_data对象,用来描述SPI控制器设备驱动操作的控制器设备,包含它的设备号,传输缓存和内嵌spi_device*/spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);if (!spidev)return -ENOMEM;/* Initialize the driver data ,将与spidev_spi驱动匹配的spi_device赋值给spidev_data.spi,通过find_first_zero_bit()从位图minors中分配到次设备号,与主设备号SPIDEV_MAJOR构成设备号并赋值给spidev_data.devt,然后调用device_create()在/dev下创建名称格式为“spidev%d%d”的字符设备文件,第一个%d表示控制器编号,即spi_device.master.bus_num,第二个对应片选号,即spi_device.chip_select*/spidev->spi = spi;spin_lock_init(&spidev->spi_lock);mutex_init(&spidev->buf_lock);INIT_LIST_HEAD(&spidev->device_entry);/* If we can allocate a minor number, hook up this device.* Reusing minors is fine so long as udev or mdev is working.*/mutex_lock(&device_list_lock);minor = find_first_zero_bit(minors, N_SPI_MINORS);if (minor < N_SPI_MINORS) {struct device *dev;spidev->devt = MKDEV(SPIDEV_MAJOR, minor);dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);status = PTR_ERR_OR_ZERO(dev);} else {dev_dbg(&spi->dev, "no minor number available!\n");status = -ENODEV;}if (status == 0) {set_bit(minor, minors);
/*将spidev_data对象添加到一个名为device_list的设备列表中,最后将spidev_data对象设置为对应spi_device的驱动数据,将其值赋值给dpi_device.dev.driver_data*/list_add(&spidev->device_entry, &device_list);}mutex_unlock(&device_list_lock);if (status == 0)spi_set_drvdata(spi, spidev);elsekfree(spidev);return status;
}

/*spidev_read(),spidev_write()实现的读写操作都是半双工的传输,要实现全双工传输需要调用spidev_ioctl()操作,在spidev_read()中,通过调用spidev_sync_read()同步读取指定长度数据到spidev_data_buffer中,然后调用copy_to_user()将读取的数据复制到用户空间缓冲*/
static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{struct spidev_data    *spidev;ssize_t            status = 0;/* chipselect only toggles at start or end of operation */if (count > bufsiz)return -EMSGSIZE;spidev = filp->private_data;mutex_lock(&spidev->buf_lock);status = spidev_sync_read(spidev, count);if (status > 0) {unsigned long    missing;missing = copy_to_user(buf, spidev->buffer, status);if (missing == status)status = -EFAULT;elsestatus = status - missing;}mutex_unlock(&spidev->buf_lock);return status;
}

/*spidev_sync_read()中定义一个spi_transfer和spi_message对象,使用spidev_data.buffer初始化spi_transfer.rx_buf,然后将spi_transfer对象添加到spi_message中,调用spidev_sync()传输信息;*/spidev_sync_read(struct spidev_data *spidev, size_t len)
{struct spi_transfer    t = {.rx_buf        = spidev->buffer,.len        = len,};struct spi_message    m;spi_message_init(&m);spi_message_add_tail(&t, &m);return spidev_sync(spidev, &m);
}

/*spidev_sync()实现同步传输的机制:首先为传入的消息对象的complete和context成员赋值,context被设置为完成接口done,complete被设置成spidev_complete(),然后
调用spi_async()启动异步传输,spi_async()返回后,通过调用wait_for_completion()阻塞调用进程,直到异步传输完成,spi_message的complete方法,即spidev_complete()
被调用来唤醒被阻塞的进程,异步传输接口函数spi_async()属于SPI核心层,在include/linux/spi/spi.h中定义并实现,它只调用了spi_device所属控制器的transfer方法*/
static ssize_t spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{DECLARE_COMPLETION_ONSTACK(done);int status;message->complete = spidev_complete;message->context = &done;spin_lock_irq(&spidev->spi_lock);if (spidev->spi == NULL)status = -ESHUTDOWN;elsestatus = spi_async(spidev->spi, message);spin_unlock_irq(&spidev->spi_lock);if (status == 0) {wait_for_completion(&done);status = message->status;if (status == 0)status = message->actual_length;}return status;
}

针对FS_S5PC100上的M25P10芯片,

//spi_device对应的含义是挂接在spi总线上的一个设备,所以描述它的时候应该明确它自身的设备特性、传输要求、及挂接在哪个总线上
static struct spi_board_info s3c_spi_devs[] __initdata = {{.modalias = "m25p10", .mode = SPI_MODE_0,    //CPOL=0, CPHA=0 此处选择具体数据传输模式.max_speed_hz = 10000000,    //最大的spi时钟频率/* Connected to SPI-0 as 1st Slave */.bus_num = 0,    //设备连接在spi控制器0上.chip_select = 0,    //片选线号,在S5PC100的控制器驱动中没有使用它作为片选的依据,而是选择了下文controller_data里的方法.controller_data = &smdk_spi0_csi[0],},};static struct s3c64xx_spi_csinfo smdk_spi0_csi[] = {[0] = {.set_level = smdk_m25p10_cs_set_level,.fb_delay = 0x3,},};static void smdk_m25p10_cs_set_level(int high)    //spi控制器会用这个方法设置cs{u32 val;val = readl(S5PC1XX_GPBDAT);if (high)val |= (1<<3);elseval &= ~(1<<3);writel(val, S5PC1XX_GPBDAT);}
spi_register_board_info(s3c_spi_devs, ARRAY_SIZE(s3c_spi_devs));//注册spi_board_info,这个代码会把spi_board_info注册要链表board_list上。
//spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_boardinfo扫描board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。
static void scan_boardinfo(struct spi_master *master){struct boardinfo *bi;mutex_lock(&board_lock);list_for_each_entry(bi, &board_list, list) {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);    //创建并注册了spi_device}}mutex_unlock(&board_lock);}
//spi_driver.c linux内核中的/driver/mtd/devices/m25p80.c驱动为参考
static struct spi_driver m25p80_driver = { //spi_driver的构建.driver = {.name = "m25p80",.bus = &spi_bus_type,.owner = THIS_MODULE,},.probe = m25p_probe,.remove = __devexit_p(m25p_remove),*/};
spi_register_driver(&m25p80_driver);//spi driver的注册
//在有匹配的spi device时,会调用m25p_probe,根据传入的spi_device参数,可以找到对应的spi_master。接下来就可以利用spi子系统为我们完成数据交互了
static int __devinit m25p_probe(struct spi_device *spi)

Step2,实现SPI platform 总线匹配

  S3C2440和S5PC100的SPI控制器驱动的实现同样采用了Platform驱动模型,针对S3C2440在drivers/spi/spi_s3c24xx.c中,模块初始化函数s3c24xx_spi_init()完成了platform驱动的注册,名称是“s3c2410-spi”,与该驱动匹配的平台设备在arch/arm/plat-samsung/devs.c中定义,并且与驱动同名,针对S5PC100,详见下面代码,匹配完成后调用相应的probe:

//Platform_device,SPI控制器对应platform_device的定义方式,同样以S5PC100中的SPI控制器为例,参看arch/arm/plat-s5pc1xx/dev-spi.c文件
struct platform_device s3c_device_spi0 = {.name = "s3c64xx-spi",     //名称,要和Platform_driver匹配.id = 0,     //第0个控制器,S5PC100中有3个控制器.num_resources = ARRAY_SIZE(s5pc1xx_spi0_resource),    //占用资源的种类.resource = s5pc1xx_spi0_resource,    //指向资源结构数组的指针.dev = {.dma_mask = &spi_dmamask,     //dma寻址范围 .coherent_dma_mask = DMA_BIT_MASK(32),     //可以通过关闭cache等措施保证一致性的dma寻址范围.platform_data = &s5pc1xx_spi0_pdata,    //特殊的平台数据,参看后文},};
static struct s3c64xx_spi_cntrlr_info s5pc1xx_spi0_pdata = {.cfg_gpio = s5pc1xx_spi_cfg_gpio,    //用于控制器管脚的IO配置.fifo_lvl_mask = 0x7f,.rx_lvl_offset = 13,};
static int s5pc1xx_spi_cfg_gpio(struct platform_device *pdev){switch (pdev->id) {case 0:s3c_gpio_cfgpin(S5PC1XX_GPB(0), S5PC1XX_GPB0_SPI_MISO0);s3c_gpio_cfgpin(S5PC1XX_GPB(1), S5PC1XX_GPB1_SPI_CLK0);s3c_gpio_cfgpin(S5PC1XX_GPB(2), S5PC1XX_GPB2_SPI_MOSI0);s3c_gpio_setpull(S5PC1XX_GPB(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(1), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(2), S3C_GPIO_PULL_UP);break;
case 1:s3c_gpio_cfgpin(S5PC1XX_GPB(4), S5PC1XX_GPB4_SPI_MISO1);s3c_gpio_cfgpin(S5PC1XX_GPB(5), S5PC1XX_GPB5_SPI_CLK1);s3c_gpio_cfgpin(S5PC1XX_GPB(6), S5PC1XX_GPB6_SPI_MOSI1);s3c_gpio_setpull(S5PC1XX_GPB(4), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(5), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(6), S3C_GPIO_PULL_UP);break;
case 2:s3c_gpio_cfgpin(S5PC1XX_GPG3(0), S5PC1XX_GPG3_0_SPI_CLK2);s3c_gpio_cfgpin(S5PC1XX_GPG3(2), S5PC1XX_GPG3_2_SPI_MISO2);s3c_gpio_cfgpin(S5PC1XX_GPG3(3), S5PC1XX_GPG3_3_SPI_MOSI2);s3c_gpio_setpull(S5PC1XX_GPG3(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(2), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(3), S3C_GPIO_PULL_UP);break;
default:dev_err(&pdev->dev, "Invalid SPI Controller number!");return -EINVAL;}
//platform_driver,参看drivers/spi/spi_s3c64xx.c文件
static struct platform_driver s3c64xx_spi_driver = {.driver = {.name = "s3c64xx-spi", //名称,和platform_device对应.owner = THIS_MODULE,},.remove = s3c64xx_spi_remove,.suspend = s3c64xx_spi_suspend,.resume = s3c64xx_spi_resume,};
platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);//注册s3c64xx_spi_driver
//和平台中注册的platform_device匹配后,调用s3c64xx_spi_probe。然后根据传入的platform_device参数,构建一个用于描述SPI控制器的结构体spi_master,并注册。spi_register_master(master)。后续注册的spi_device需要选定自己的spi_master,并利用spi_master提供的传输功能传输spi数据。和I2C类似,SPI也有一个描述控制器的对象叫spi_master,其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等;
struct spi_master {struct device    dev;s16  bus_num;     //表示是SPI主机控制器的编号。由平台代码决定u16  num_chipselect;    //控制器支持的片选数量,即能支持多少个spi设备int  (*setup)(struct spi_device *spi);    //针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用。int  (*transfer)(struct spi_device *spi,struct spi_message *mesg);    //实现数据的双向传输,可能会睡眠void       (*cleanup)(struct spi_device *spi);    //注销时调用};

static int s3c24xx_spi_probe(struct platform_device *pdev)
{struct s3c2410_spi_info *pdata;struct s3c24xx_spi *hw;struct spi_master *master;struct resource *res;int err = 0;master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));//分配SPI控制器结构及驱动私有数据if (master == NULL) {dev_err(&pdev->dev, "No memory for spi_master\n");return -ENOMEM;}hw = spi_master_get_devdata(master);memset(hw, 0, sizeof(struct s3c24xx_spi));hw->master = master;hw->pdata = pdata = dev_get_platdata(&pdev->dev);hw->dev = &pdev->dev;if (pdata == NULL) {dev_err(&pdev->dev, "No platform data supplied\n");err = -ENOENT;goto err_no_pdata;}platform_set_drvdata(pdev, hw);//将SPI控制器私有数据作为平台设备驱动数据,便于通过相应接口获得init_completion(&hw->done);//初始化完成接口/* initialise fiq handler */s3c24xx_spi_initfiq(hw);/* setup the master state. *//* the spi->mode bits understood by this driver: */master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;master->num_chipselect = hw->pdata->num_cs;//设置控制器片选总数和总线编号master->bus_num = pdata->bus_num;/* setup the state for the bitbang driver 初始化bitbang驱动的相关成员*/hw->bitbang.master         = hw->master;hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;hw->bitbang.chipselect     = s3c24xx_spi_chipsel;hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;hw->master->setup  = s3c24xx_spi_setup;hw->master->cleanup = s3c24xx_spi_cleanup;dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);/* find and map our resources 这是平台驱动中最常规的工作,找到并映射资源*/res = platform_get_resource(pdev, IORESOURCE_MEM, 0);hw->regs = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(hw->regs)) {err = PTR_ERR(hw->regs);goto err_no_pdata;}hw->irq = platform_get_irq(pdev, 0);if (hw->irq < 0) {dev_err(&pdev->dev, "No IRQ specified\n");err = -ENOENT;goto err_no_pdata;}err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0,pdev->name, hw);if (err) {dev_err(&pdev->dev, "Cannot claim IRQ\n");goto err_no_pdata;}hw->clk = devm_clk_get(&pdev->dev, "spi");//获取SPI的时钟资源if (IS_ERR(hw->clk)) {dev_err(&pdev->dev, "No clock for device\n");err = PTR_ERR(hw->clk);goto err_no_pdata;}/* setup any gpio we can 设置片选方法并配置片选引脚 */if (!pdata->set_cs) {if (pdata->pin_cs < 0) {dev_err(&pdev->dev, "No chipselect pin\n");err = -EINVAL;goto err_register;}err = devm_gpio_request(&pdev->dev, pdata->pin_cs,dev_name(&pdev->dev));if (err) {dev_err(&pdev->dev, "Failed to get gpio for cs\n");goto err_register;}hw->set_cs = s3c24xx_spi_gpiocs;gpio_direction_output(pdata->pin_cs, 1);} elsehw->set_cs = pdata->set_cs;s3c24xx_spi_initialsetup(hw);//使能SPI时钟,初始化2440 SPI控制器寄存器及片选引脚等/* register our spi controller 注册SPI控制器,进而完成对控制器对象的分配初始化和注册,注册控制器会扫描board_list链表,从中取出spi_board_info创建spi_device设备,因此在执行probe方法时,只要board_list链表上有控制器对应的spi_board_info,就能创建出控制器的设备对象,并与控制器设备驱动匹配,进而创建出用于访问该控制器的设备文件*/err = spi_bitbang_start(&hw->bitbang);if (err) {dev_err(&pdev->dev, "Failed to register SPI master\n");goto err_register;}return 0;err_register:clk_disable(hw->clk);err_no_pdata:spi_master_put(hw->master);return err;
}

Step3,分析实现SPI总线通信方法

  SPI控制器通常由spi_bitbang来完成实际数据的数据传输,spi_bitbang的定义如下:

struct spi_bitbang {spinlock_t        lock;   /*操作工作队列时使用的自旋锁*/u8            busy;u8            use_dma;u8            flags;        /* extra spi->mode support */struct spi_master    *master;/* setup_transfer() changes clock and/or wordsize to match settings* for this transfer; zeroes restore defaults from spi_device.为特定的传输设置时钟、字宽等属性的方法*/int    (*setup_transfer)(struct spi_device *spi,struct spi_transfer *t);void    (*chipselect)(struct spi_device *spi, int is_on);
#define    BITBANG_CS_ACTIVE    1    /* normally nCS, active low */
#define    BITBANG_CS_INACTIVE    0/* txrx_bufs()为实际的传输方法 may handle dma mapping for transfers that don't* already have one (transfer.{tx,rx}_dma is zero), or use PIO*/int    (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);/* txrx_word[SPI_MODE_*]()按字传输的方法 just looks like a shift register */u32    (*txrx_word[4])(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits);
};

  两者的SPI总线通信控制方法不同,在上面spi_bitbang结构可以看出,消息传输启动利用的是spi_bibang_txrx_bufs(),在2440 SPI驱动中是通过s3c24xx_spi_txrx()来实现,他将传输段中请求数据的缓冲区和长度赋值给s3c24xx_spi对象的相关成员,然后主动发送一个字节数据,启动传输过程,之后使用进程阻塞在完成接口s3c24xx_spi.done上,其他数据的传输将在中断处理函数中完成,中断检查到发送完成后,通知完成接口,唤醒之前阻塞在其上的进程(这里特别说明下6410SPI通信方法,与2440不同,采用DMA传输,相应的实现函数为s3c64xx_spi_transfer(),利用工作队列进行延迟调度),m25p80SPI总线通信控制详尽代码如下,说明一点,spi_message的消息可以同步传输也可以异步传输,正常产品中基本上都是异步传输,传输结束时有spi_message.complete()方法完成,下面的例子以同步实现简单实现:

#include <linux/platform_device.h> #include <linux/spi/spi.h> #include <linux/init.h>#include <linux/module.h>#include <linux/device.h>#include <linux/interrupt.h>#include <linux/mutex.h>#include <linux/slab.h> // kzalloc#include <linux/delay.h>
#define FLASH_PAGE_SIZE     256
/* Flash Operating Commands */#define CMD_READ_ID                 0x9f#define CMD_WRITE_ENABLE    0x06    #define CMD_BULK_ERASE        0xc7#define CMD_READ_BYTES        0x03#define CMD_PAGE_PROGRAM    0x02#define CMD_RDSR            0x05
/* Status Register bits. */#define SR_WIP           1   /* Write in progress */#define SR_WEL          2   /* Write enable latch */
/* ID Numbers */#define MANUFACTURER_ID     0x20#define DEVICE_ID           0x1120
/* Define max times to check status register before we give up. */#define MAX_READY_WAIT_COUNT    100000#define CMD_SZ  4
struct m25p10a {struct spi_device   *spi;struct mutex        lock;char    erase_opcode;char    cmd[ CMD_SZ ];};
/** Internal Helper functions */
/** Read the status register, returning its value in the location* Return the status register value.* Returns negative if error occurred.*/static int read_sr(struct m25p10a *flash){ssize_t retval;u8 code = CMD_RDSR;u8 val;retval = spi_write_then_read(flash->spi, &code, 1, &val, 1);if (retval < 0) {dev_err(&flash->spi->dev, "error %d reading SR\n", (int) retval);return retval;}return val;}
/** Service routine to read status register until ready, or timeout occurs.* Returns non-zero if error.*/static int wait_till_ready(struct m25p10a *flash){int count;int sr;/* one chip guarantees max 5 msec wait here after page writes,* but potentially three seconds (!) after page erase.*/for (count = 0; count < MAX_READY_WAIT_COUNT; count++) {if ((sr = read_sr(flash)) < 0)break;else if (!(sr & SR_WIP))return 0;/* REVISIT sometimes sleeping would be best */}   printk( "in (%s): count = %d\n", count );return 1;}
/** Set write enable latch with Write Enable command.* Returns negative if error occurred.*/static inline int write_enable( struct m25p10a *flash ){flash->cmd[0] = CMD_WRITE_ENABLE;return spi_write( flash->spi, flash->cmd, 1 );}
/** Erase the whole flash memory** Returns 0 if successful, non-zero otherwise.*/static int erase_chip( struct m25p10a *flash ){/* Wait until finished previous write command. */if (wait_till_ready(flash))return -1;/* Send write enable, then erase commands. */write_enable( flash );flash->cmd[0] = CMD_BULK_ERASE;return spi_write( flash->spi, flash->cmd, 1 );}
/** Read an address range from the flash chip.  The address range* may be any size provided it is within the physical boundaries.*/static int m25p10a_read( struct m25p10a *flash, loff_t from,  size_t len, char *buf ){int r_count = 0, i;flash->cmd[0] = CMD_READ_BYTES;flash->cmd[1] = from >> 16;flash->cmd[2] = from >> 8;flash->cmd[3] = from;#if 1struct spi_transfer st[2];struct spi_message  msg;spi_message_init( &msg );memset( st, 0, sizeof(st) );flash->cmd[0] = CMD_READ_BYTES;flash->cmd[1] = from >> 16;flash->cmd[2] = from >> 8;flash->cmd[3] = from;st[ 0 ].tx_buf = flash->cmd;st[ 0 ].len = CMD_SZ;spi_message_add_tail( &st[0], &msg );st[ 1 ].rx_buf = buf;st[ 1 ].len = len;spi_message_add_tail( &st[1], &msg );mutex_lock( &flash->lock );/* Wait until finished previous write command. */if (wait_till_ready(flash)) {mutex_unlock( &flash->lock );return -1;}spi_sync( flash->spi, &msg );r_count = msg.actual_length - CMD_SZ;printk( "in (%s): read %d bytes\n", __func__, r_count );for( i = 0; i < r_count; i++ ) {printk( "0x%02x\n", buf[ i ] );}mutex_unlock( &flash->lock );#endifreturn 0;}
/** Write an address range to the flash chip.  Data must be written in* FLASH_PAGE_SIZE chunks.  The address range may be any size provided* it is within the physical boundaries.*/static int m25p10a_write( struct m25p10a *flash, loff_t to, size_t len, const char *buf ){int w_count = 0, i, page_offset;struct spi_transfer st[2];struct spi_message  msg;#if 1if (wait_till_ready(flash)) {    //读状态,等待readymutex_unlock( &flash->lock );return -1;}#endifwrite_enable( flash );  //写使能 spi_message_init( &msg );memset( st, 0, sizeof(st) );flash->cmd[0] = CMD_PAGE_PROGRAM;flash->cmd[1] = to >> 16;flash->cmd[2] = to >> 8;flash->cmd[3] = to;st[ 0 ].tx_buf = flash->cmd;st[ 0 ].len = CMD_SZ;spi_message_add_tail( &st[0], &msg );st[ 1 ].tx_buf = buf;st[ 1 ].len = len;spi_message_add_tail( &st[1], &msg );mutex_lock( &flash->lock );/* get offset address inside a page */page_offset = to % FLASH_PAGE_SIZE;    /* do all the bytes fit onto one page? */if( page_offset + len <= FLASH_PAGE_SIZE ) {    // yesst[ 1 ].len = len; printk("%d, cmd = %d\n", st[ 1 ].len, *(char *)st[0].tx_buf);//while(1){spi_sync( flash->spi, &msg );}w_count = msg.actual_length - CMD_SZ;}else {  // no}printk( "in (%s): write %d bytes to flash in total\n", __func__, w_count );mutex_unlock( &flash->lock );return 0;}static int check_id( struct m25p10a *flash ) { char buf[10] = {0}; flash->cmd[0] = CMD_READ_ID;spi_write_then_read( flash->spi, flash->cmd, 1, buf, 3 ); printk( "Manufacture ID: 0x%x\n", buf[0] );printk( "Device ID: 0x%x\n", buf[1] | buf[2]  << 8 );return buf[2] << 16 | buf[1] << 8 | buf[0]; }
static int m25p10a_probe(struct spi_device *spi) { int ret = 0;struct m25p10a  *flash;char buf[ 256 ];printk( "%s was called\n", __func__ );flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL );if( !flash ) {return -ENOMEM;}flash->spi = spi;mutex_init( &flash->lock );/* save flash as driver's private data */spi_set_drvdata( spi, flash );check_id( flash );    //读取ID#if 1ret = erase_chip( flash );  //擦除 if( ret < 0 ) {printk( "erase the entirely chip failed\n" );}printk( "erase the whole chip done\n" );memset( buf, 0x7, 256 );m25p10a_write( flash, 0, 20, buf); //0地址写入20个7memset( buf, 0, 256 );m25p10a_read( flash, 0, 25, buf ); //0地址读出25个数 #endifreturn 0; }
static int m25p10a_remove(struct spi_device *spi) { return 0; }
static struct spi_driver m25p10a_driver = { .probe = m25p10a_probe, .remove = m25p10a_remove, .driver = { .name = "m25p10a", }, };
static int __init m25p10a_init(void) { return spi_register_driver(&m25p10a_driver); }
static void __exit m25p10a_exit(void) { spi_unregister_driver(&m25p10a_driver); }
module_init(m25p10a_init); module_exit(m25p10a_exit);
MODULE_DESCRIPTION("m25p10a driver for FS_S5PC100");
MODULE_LICENSE("GPL");

SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)相关推荐

  1. SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)_0

    由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件 ...

  2. DM9000驱动移植在mini2440(linux2.6.29)和FS4412(linux3.14.78)上的实现(deep dive)

    关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(Cortex A9)上实现,基 ...

  3. Cortex、ARMv8、arm架构、ARM指令集、soc?Cortex A8、A9都是ARMv7a 架构;Cortex M3、M4是ARMv7m架构;前者是处理器(内核)后者是指令集的架构(架构)

    架构组成元素的指令集状态或者语法thumb指令集与arm指令集的区别例如thumb指令集是什么_thumb指令集与arm指令集的区别以及thumb-2的关系在下一文中介绍,本文暂时不讨论 有粉丝问我到 ...

  4. 【原创】gooogleman亲自参与设计的三星Cortex A8 S5pv210 之Sate210核心板硬件用户手册(作者:gooogleman)...

    作者:gooogleman                                 时间:2011.09 从2011年四月份开始筹划设计一款S5pv210方案至今,已经有五个多月,期间还做了一 ...

  5. Cortex A8 LED 点亮程序分析

    1     Cortex A8 LED 点亮程序分析 1.1     环境搭建准备工作 软件环境:            1.下载eclipse软件(绿色软件) 2.安装"yagarto-b ...

  6. S3C6410嵌入式应用平台构建(六)——linux-3.14.4移植到OK6410-(Yaffs2文件系统移植)...

    我个人觉得nandflash上用yaffs2文件系统是很好的方案,但是最新的Linux并不支持yaffs2文件系统,需要你自己给内核打补丁,不过话说在前面,由于内核间差异及兼容问题,在编译时肯定会出现 ...

  7. RVDS4.0上开发cortex A8 NEON程序

    用RVDS4.0集成开发环境新建一个target为cortex A8 NEON的工程,即便是hellowolrd,也无法正常运行, 然而自己写makefile调用amrcc的方式就可以.在水木等地咨询 ...

  8. imx6的Linux默认颜色,MY-IMX6 Linux-3.14 测试手册(1)

    明远智睿MY-IMX6 Linux-3.14 测试手册 1 测试前的准备 请按照<Linux快速启动手册>中的"Linux快速启动" -> "连接设备& ...

  9. 新年开工第一篇文章——推荐几个值得中小企业使用的ARM9/ARM11/Cortex A8处理器

    //toppic:推荐几个值得中小企业使用的ARM9/ARM11/Cortex A8处理器 // 作者:gooogleman //原文地址:http://blog.csdn.net/goooglema ...

最新文章

  1. 毒霸主程序集成反流氓
  2. 使用photoshop 10.0制作符合社保要求的照片
  3. Android 4.2真坑爹
  4. 索引扫描、查找、书签查询、覆盖查询示例介绍
  5. 倚天·屠龙——唯我独尊
  6. 【JavaScript】【JQuery】获取 data-* 属性值
  7. python可变序列_python序列中可变数据类型有什么
  8. Android recyclerView/listview的点击变色、点击换背景颜色
  9. 数据结构与算法--死磕二叉树
  10. Android实现拍照并上传
  11. thinkphp路由配置 php7.0,Thinkphp url路由配置
  12. 和10位CIO,聊了聊他们今年的OKR
  13. itop4412的安卓驱动移植
  14. 如何批量将 Xlsx 格式的 Excel 文档转为 Xls 格式
  15. CHERRY樱桃机械键盘按键
  16. 张小龙2019微信公开课演讲实录
  17. 解析DeDecms系统的SEO内部优化技巧
  18. WPA3 vs WPA2
  19. Win10 Windows Defender 保护历史记录清空方法
  20. SQL leetcode 刷题答案(二)

热门文章

  1. html怎么设置两块区域,将两个视频一左一右拼接 可裁剪画面并设置视频画面大小及位置...
  2. 计算机思维游戏,思维数字 电脑版
  3. CPT-1166/1266蓝牙无线扫描枪
  4. 【莫烦Python】机器要说话 NLP 自然语言处理教程 W2V Transformer BERT Seq2Seq GPT 笔记
  5. 技术思维VS管理思维
  6. 关于Paxos的历史
  7. QA的职责和角色定位
  8. [源码]Meepo路由
  9. CSS和HTML基操
  10. 初中动画flash作品_初中动画flash作品_初中Flash动画设计基础知识试题及答案.doc...