RT-thread 设备驱动组件之SPI设备

本文主要介绍RT-thread中的SPI设备驱动,涉及到的文件主要有:spi_dev.c,spi_core.c,spi.h,spi_hard.c,spi_hard.h。

一、SPI设备框架

先来看spi.h中的一些数据结构:

**
 * SPI message structure
 */
struct rt_spi_message
{
    const void *send_buf;
    void *recv_buf;
    rt_size_t length;
    struct rt_spi_message *next;

    unsigned cs_take    : 1;
    unsigned cs_release : 1;
};

/**
 * SPI configuration structure
 */
struct rt_spi_configuration
{
    rt_uint8_t mode;
    rt_uint8_t data_width;
    rt_uint16_t reserved;

    rt_uint32_t max_hz;
};

struct rt_spi_ops;
struct rt_spi_bus
{
    struct rt_device parent;
    const struct rt_spi_ops *ops;

    struct rt_mutex lock;
    struct rt_spi_device *owner;
};

/**
 * SPI operators
 */
struct rt_spi_ops
{
    rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
    rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};
/**
 * SPI Virtual BUS, one device must connected to a virtual BUS
 */
struct rt_spi_device
{
    struct rt_device parent;
    struct rt_spi_bus *bus;

    struct rt_spi_configuration config;
};
#define SPI_DEVICE(dev) ((struct rt_spi_device *)(dev))

spi_core.c,spi_dev.c这两个文件位于RTT\components\drivers\spi目录下,而spi.h头文件位于RTT\\components\drivers\include\drivers目录下。可在MKD工程的Drivers组下将上面两个源文件加进行,并将spi.h头文件所在目录添加到工程的include path下。

spi_core.c文件实现了spi的抽象操作,如注册spi总线(spi_bus),向SPI总线添加设备函数等。注: 这里将MCU的一路spi外设虚拟成spi总线,然后总线上可以挂很多spi设备(spi_device),一个spi_device有一个片选cs。spi总线和spi设备要在RTT中可以生效就必须先向RTT注册,因此就需要使用上面的注册SPI总线函数和向SPI总线中添加SPI设备。

spi_core.c还包含了配置SPI函数,发送和接收等通信函数,占用和释放SPI总线函数及选择SPI设备函数。这些函数都是抽象出来的,反映出SPI总线上的一些常规操作。真正执行这些操作的过程并不在spi_core.c源文件中,实际上,这些操作信息都是通过注册SPI总线和向总线添加SPI设备时这些操作集就已经"注册"下来了,真正操作时是通过注册信息内的操作函数去实现,也可以说是一种回调操作。spi_core.c中实现的函数主要有:rt_spi_bus_register(); rt_spi_bus_attach_device(); rt_spi_configure(); rt_spi_send_then_send(); rt_spi_send_then_recv(); rt_spi_transfer(); rt_spi_transfer_message(); rt_spi_take_bus(); rt_spi_release_bus(); rt_spi_take(); rt_spi_release()。

而spi_dev.c实现了SPI设备的一些抽象操作,比如读,写,打开,关闭,初始化等,当然当MCU操作SPI设备的时候,是需要通过SPI总线与SPI设备进行通信的,既然通信就必然会有SPI通信协议,但是通信协议并不在这里具体,spi_dev.c这里还只是SPI设备的抽象操作而已,它只是简单地调用spi_core.c源文件中的抽象通信而已,具体实现还是要靠上层通过SPI总线或SPI设备注册下来的信息而实现的。spi_device.c中实现的函数主要有:_spi_bus_device_read();  _spi_bus_device_write();  _spi_bus_device_control();  rt_spi_bus_device_init();_spidev_device_read();_spidev_device_write();_spidev_device_control();rt_spidev_device_init()。

在确保了spi_core.c,spi_dev.c和spi.h这三个源文件在MDK工程内之后,接着往下走。

二、SPI底层硬件驱动

在spi_hard.c中实现configure和xfer函数:

static struct rt_spi_ops stm32_spi_ops =
{
    configure,
    xfer
};

然后,向RT-thread注册spi总线:

struct stm32_spi_bus
{
    struct rt_spi_bus parent;

    SPI_TypeDef * SPI;

#ifdef SPI_USE_DMA
    DMA_Stream_TypeDef * DMA_Stream_TX;
    uint32_t DMA_Channel_TX;

    DMA_Stream_TypeDef * DMA_Stream_RX;
    uint32_t DMA_Channel_RX;

    uint32_t DMA_Channel_TX_FLAG_TC;
    uint32_t DMA_Channel_RX_FLAG_TC;
#endif /* #ifdef SPI_USE_DMA */
};

struct stm32_spi_cs
{
    GPIO_TypeDef * GPIOx;
    uint16_t GPIO_Pin;
};
rt_err_t stm32_spi_register(SPI_TypeDef * SPI,
                            struct stm32_spi_bus * stm32_spi,
                            const char * spi_bus_name)
{
    if(SPI == SPI1)
    {
        stm32_spi->SPI = SPI1;
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//84MHZ

#ifdef SPI_USE_DMA
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
        /* DMA2_Stream0 DMA_Channel_3 : SPI1_RX ; DMA2_Stream2 DMA_Channel_3 : SPI1_RX */
        stm32_spi->DMA_Stream_RX = DMA2_Stream0;
        stm32_spi->DMA_Channel_RX = DMA_Channel_3;
        stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF0;
        /* DMA2_Stream3 DMA_Channel_3 : SPI1_TX ; DMA2_Stream5 DMA_Channel_3 : SPI1_TX */
        stm32_spi->DMA_Stream_TX = DMA2_Stream3;
        stm32_spi->DMA_Channel_TX = DMA_Channel_3;
        stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF3;
#endif
    }
    else if(SPI == SPI2)
    {
      stm32_spi->SPI = SPI2;
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//42MHZ

#ifdef SPI_USE_DMA
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
        /* DMA1_Stream3 DMA_Channel_0 : SPI2_RX */
        stm32_spi->DMA_Stream_RX = DMA1_Stream3;
        stm32_spi->DMA_Channel_RX = DMA_Channel_0;
        stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF3;
        /* DMA1_Stream4 DMA_Channel_0 : SPI2_TX */
        stm32_spi->DMA_Stream_TX = DMA1_Stream4;
        stm32_spi->DMA_Channel_TX = DMA_Channel_0;
        stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF4;
#endif
    }
    else if(SPI == SPI3)
    {
        stm32_spi->SPI = SPI3;
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//42MHZ

#ifdef SPI_USE_DMA
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
                /* DMA1_Stream2 DMA_Channel_0 : SPI3_RX ; DMA1_Stream0 DMA_Channel_0 : SPI3_RX */
                stm32_spi->DMA_Stream_RX = DMA1_Stream2;
                stm32_spi->DMA_Channel_RX = DMA_Channel_0;
                stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF2;
                /* DMA1_Stream5 DMA_Channel_0 : SPI3_TX ; DMA1_Stream7 DMA_Channel_0 : SPI3_TX */
                stm32_spi->DMA_Stream_TX = DMA1_Stream5;
                stm32_spi->DMA_Channel_TX = DMA_Channel_0;
                stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF5;
#endif
    }
    else
    {
        return RT_ENOSYS;
    }

    return rt_spi_bus_register(&stm32_spi->parent, spi_bus_name, &stm32_spi_ops);
}

最后,进行spi硬件初始化,并挂载spi设备到已注册的spi总线。

int rt_hw_spi1_init(void)
{
    /* register SPI bus */
    static struct stm32_spi_bus stm32_spi;             //it must be add static

    /* SPI1 configure */
    {
        GPIO_InitTypeDef GPIO_InitStructure;

        /* Enable GPIO Periph clock */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA , ENABLE);

        GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);

        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL; 

        /* Configure SPI1 pins */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    } /* SPI1 configuration */

    /* register SPI1 to stm32_spi_bus */
    stm32_spi_register(SPI1, &stm32_spi, "spi1");

    /* attach spi10 */
    {
        static struct rt_spi_device rt_spi_device_10;    //it must be add static
        static struct stm32_spi_cs  stm32_spi_cs_10;     //it must be add static

        stm32_spi_cs_10.GPIOx    = GPIOE;
        stm32_spi_cs_10.GPIO_Pin = GPIO_Pin_3;

        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
        GPIO_Init(GPIOE, &GPIO_InitStructure);
        GPIO_SetBits(GPIOE, GPIO_Pin_3);

        rt_spi_bus_attach_device(&rt_spi_device_10, "spi10", "spi1", (void*)&stm32_spi_cs_10);//set spi_device->bus
        /* config spi */
        {
            struct rt_spi_configuration cfg;
            cfg.data_width = 8;
            cfg.mode = RT_SPI_MODE_3 | RT_SPI_MSB; /* SPI Compatible Modes 3 and SPI_FirstBit_MSB in lis302dl datasheet */

            //APB2=168M/2=84M, SPI1 = 84/2,4,8,16,32 = 42M, 21M, 10.5M, 5.25M, 2.625M ...
            cfg.max_hz = 2625000; /* SPI_BaudRatePrescaler_16=84000000/16=5.25MHz. The max_hz of lis302dl is 10MHz in datasheet */
            rt_spi_configure(&rt_spi_device_10, &cfg);
        } /* config spi */
    } /* attach spi10 */    

    return 0;
}
INIT_BOARD_EXPORT(rt_hw_spi1_init);//rt_hw_spi1_init will be called in rt_components_board_init()

三、SPI设备初始化

这里以lis302dl三轴加速度计为例:

static rt_err_t lis302dl_init(const char * spi_device_name)
{
    rt_uint8_t chip_id, ctrl, temp;

    spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name);
    if(spi_device == RT_NULL)
  {
     rt_kprintf("\nspi_device %s for lis302dl not found!\n", spi_device_name);
     return -RT_ENOSYS;
  }

//  /* If not use rt_device_write or rt_device_read, then it‘s no necessary to rt_device_open */
//    /* oflag has no meaning for spi device , so set to RT_NULL */
//    if(rt_device_open(&spi_device->parent, RT_NULL) != RT_EOK)
//    {
//        rt_kprintf("\nspi_device %s for lis302dl opened failed!\n", spi_device_name);
//        return -RT_EEMPTY;
//    }

  LIS302DL_InitTypeDef  LIS302DL_InitStruct;
    LIS302DL_FilterConfigTypeDef LIS302DL_FilterStruct;
    /* Set configuration of LIS302DL*/
    LIS302DL_InitStruct.Output_DataRate = LIS302DL_DATARATE_100;
  LIS302DL_InitStruct.Power_Mode      = LIS302DL_LOWPOWERMODE_ACTIVE;
  LIS302DL_InitStruct.Full_Scale      = LIS302DL_FULLSCALE_2_3;
    LIS302DL_InitStruct.Self_Test       = LIS302DL_SELFTEST_NORMAL;
  LIS302DL_InitStruct.Axes_Enable     = LIS302DL_XYZ_ENABLE;
  LIS302DL_Init(&LIS302DL_InitStruct);
    /* MEMS High Pass Filter configuration */
    LIS302DL_FilterStruct.HighPassFilter_Data_Selection   = LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER;
    LIS302DL_FilterStruct.HighPassFilter_Interrupt        = LIS302DL_HIGHPASSFILTERINTERRUPT_1_2;
    LIS302DL_FilterStruct.HighPassFilter_CutOff_Frequency = LIS302DL_HIGHPASSFILTER_LEVEL_1;
    LIS302DL_FilterConfig(&LIS302DL_FilterStruct);

  /* not use internal high pass filter and INT2 */
    ctrl=0x04;//enable INT1 Data ready interrupt; interrupt active high; pull-push;
    LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG3_ADDR, 1);
    LIS302DL_Read(&temp, LIS302DL_CTRL_REG3_ADDR, 1);
    if(temp == ctrl)
        rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify passed!\n", temp);
    else
        rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify failed!\n", temp);

    /* Required delay for the MEMS Accelerometre: Turn-on time = 3/Output data Rate = 3/100 = 30ms in datasheet */
    //rt_thread_delay(30);
    extern void stm32_mdelay(rt_uint32_t ms);
    stm32_mdelay(30);    

  /* power_mode is active */
  LIS302DL_Read(&chip_id, LIS302DL_WHO_AM_I_ADDR, 1);
  rt_kprintf("(chip_id of lis302dl is 0x%02x)", chip_id); 

    return 0;
}

int rt_lis302dl_init(void)
{
    rt_sem_init(&sem_lis302dl, "lis302dl", 0, RT_IPC_FLAG_FIFO);

    lis302dl_interrupt_int1();

    lis302dl_init("spi10");

    return 0;
}
INIT_APP_EXPORT(rt_lis302dl_init);

注:

1、若需要使用rt_device_read()或rt_device_write()函数,则必须先调用rt_device_open()打开spi设备,保证该设备的ref_count大于0。硬件初始化函数中不需要调用rt_device_open()打开spi总线,因为在rt_spi_bus_attach_device()函数中没有初始化bus->owner,从而会导致调用_spi_bus_device_read()或_spi_bus_device_write()时“RT_ASSERT(bus->owner != RT_NULL);”断言语句进入死循环。而 _spidev_device_read()或_spidev_device_write()中断言语句“RT_ASSERT(device->bus != RT_NULL);”正常通过。

2、在使用SPI设备驱动操作数字芯片的寄存器时,需谨慎使用rt_device_read()和rt_device_write()函数。因为根据spi读写时序,spi读写一次最少要连续操作2个字节数据(第一个为寄存器地址值,第二个为待读取或待写入的字节数据),并且在这2个字节数据之间CS信号不能拉高,而rt_device_read()和rt_device_write()函数仅操作一个字节后,cs信号拉高,导致字节数据不能正常读取或写入相应寄存器。所以,一般情况下在SPI工作在全双工模式时,读写数字芯片寄存器的函数中直接使用spi_core.c中的rt_spi_transfer()、rt_spi_send_then_recv()、rt_spi_send_then_send()三个函数,如下所示:

void LIS302DL_Write(rt_uint8_t* pBuffer, rt_uint8_t WriteAddr, rt_uint16_t NumByteToWrite)
{
    /* Configure the MS bit:
       - When 0, the address will remain unchanged in multiple read/write commands.
       - When 1, the address will be auto incremented in multiple read/write commands.
  */
  if(NumByteToWrite > 0x01)
  {
    WriteAddr |= (rt_uint8_t)MULTIPLEBYTE_CMD;
  }

    /* the CS can‘t pull up between &WriteAddr and pBuffer */
  //rt_device_write(&spi_device->parent, RT_NULL, &WriteAddr, 1);
  //rt_device_write(&spi_device->parent, RT_NULL, pBuffer, NumByteToWrite);
    rt_spi_send_then_send(spi_device, &WriteAddr, 1, pBuffer, NumByteToWrite);// transfer NumByteToWrite+1 bytes
}

void LIS302DL_Read(rt_uint8_t* pBuffer, rt_uint8_t ReadAddr, rt_uint16_t NumByteToRead)
{
    /* Configure the MS bit:
       - When 0, the address will remain unchanged in multiple read/write commands.
       - When 1, the address will be auto incremented in multiple read/write commands.
  */
    if(NumByteToRead > 0x01)
  {
    ReadAddr |= (rt_uint8_t)(READWRITE_CMD | MULTIPLEBYTE_CMD);
  }
  else
  {
    ReadAddr |= (rt_uint8_t)READWRITE_CMD;
  }

    /* the CS can‘t pull up between &WriteAddr and pBuffer */
    //rt_device_write(&spi_device->parent, RT_NULL, &ReadAddr, 1);
    //rt_device_read(&spi_device->parent, RT_NULL, pBuffer, NumByteToRead);
    rt_spi_send_then_recv(spi_device, &ReadAddr, 1, pBuffer, NumByteToRead);// transfer NumByteToRead+1 bytes
}

3、对于可读取寄存器值的数字芯片,在写入字节数据后可通过读取相同寄存器,判断读出的值与写入的值是否一致,从而判断寄存器写操作是否正确,如下:

void LIS302DL_Init(LIS302DL_InitTypeDef *LIS302DL_InitStruct)
{
    rt_uint8_t ctrl = 0x00;

    /* Configure MEMS: data rate, power mode, full scale, self test and axes */
  ctrl = (rt_uint8_t) (LIS302DL_InitStruct->Output_DataRate | LIS302DL_InitStruct->Power_Mode |                     LIS302DL_InitStruct->Full_Scale | LIS302DL_InitStruct->Self_Test |                     LIS302DL_InitStruct->Axes_Enable);

    /* Write value to MEMS CTRL_REG1 regsister */
  LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG1_ADDR, 1);

    rt_uint8_t temp = 0x00;
    LIS302DL_Read(&temp,  LIS302DL_CTRL_REG1_ADDR, 1);
    if(temp == ctrl)
        rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify passed!\n", temp);
    else
        rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify failed!\n", temp);
}
时间: 2024-08-11 01:23:47

RT-thread 设备驱动组件之SPI设备的相关文章

RT-thread 设备驱动组件之pin设备

在RT-thread 2.0.0正式版中引入了pin设备作为杂类设备,其设备驱动文件pin.c在rt-thread-2.0.1\components\drivers\misc中,主要用于操作芯片GPIO, 如点亮led,按键等.同时对于相应的芯片平台,需要自行编写底层gpio驱动,如gpio.c. 一.在pin.c中定义了一个静态的pin设备对象static struct rt_device_pin _hw_pin,其中结构体类型struct rt_device_pin在pin.h中定义为: /

20150226 IMX257 总线设备驱动模型编程之设备篇

20150226 IMX257 总线设备驱动模型编程之设备篇 2015-02-26 李海沿 前面我们呢实现了总线-设备-驱动模型中的总线,自然,我们的目标就是在我们建立的总线下面创建一个设备. http://www.cnblogs.com/lihaiyan/p/4301072.html 一.程序分析 1. 包含总线 既然我们的设备在总线上,自然我们既要包含总线了 如图所示,使用外部声明将我们的总线的结构体包含进来 2. 定义设备结构体 父目录为 my_bus 3. 定义属性文件结构体 属性文件结

RT-thread 设备驱动组件之IIC总线设备

本文主要介绍RT-thread中IIC总线设备驱动,涉及到的主要文件有:驱动框架文件(i2c_core.c,i2c_dev.c,i2c-bit-ops.c,i2c_dev.h,i2c.h):底层硬件驱动文件(i2c_soft.c,i2c_soft.h).这里的i2c_soft.c和i2c_soft.h是指利用MCU的GPIO口模拟IIC总线时序,而不是利用MCU的硬件IIC接口.应用IIC总线设备驱动时,需要在rtconfig.h中添加宏定义#define RT_USING_I2C.若使用GPI

linux设备驱动第一篇:设备驱动程序简介

首先,我们知道驱动是内核的一部分,那么驱动在内核中到底扮演了什么角色呢? 设备驱动程序在内核中的角色:他们是一个个独立的“黑盒子”,使某个特定的硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节.(说白了,驱动程序除了对外提供特定的接口外,任何实现细节对应用程序都是不可见的.)用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序.驱动程序的任务是把这些标准化调用映射到实际硬件的设备特有操作上. 在编写驱动程序时,程序员应该特别注意下面这个概念:编写访问硬件的内核代

Linux 设备驱动--- 阻塞型字符设备驱动 --- O_NONBLOCK --- 非阻塞标志【转】

转自:http://blog.csdn.net/yikai2009/article/details/8653697 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 阻塞 阻塞操作 非阻塞操作 阻塞方式-read- 实现 阻塞方式-write- 实现 非阻塞方式的读写操作 实例 --- 读阻塞的实现 实例 --- 按键驱动阻塞实现 1在 open 函数 查看看是 阻塞方式 还是 非阻塞方式 2在 read 函数中同样查看 3应用程序中 1以阻塞方式运行 2以非阻塞方式运行

Linux设备驱动编程之复杂设备驱动

这里所说的复杂设备驱动涉及到PCI.USB.网络设备.块设备等(严格意义而言,这些设备在概念上并不并列,例如与块设备并列的是字符设备,而PCI.USB设备等都可能属于字符设备),这些设备的驱动中又涉及到一些与特定设备类型相关的较为复杂的数据结构和程序结构.本文将不对这些设备驱动的细节进行过多的介绍,仅仅进行轻描淡写的叙述. PCI 是The Peripheral Component Interconnect -Bus的缩写,CPU使用PCI桥chipset与PCI设备通信,PCI桥chipset

linux驱动开发(三) 字符设备驱动框架(自动创建设备节点)

代码如下 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> int demo_major = 250; int demo_minor = 0; int demo_count = 1; stru

Linux设备驱动实现自动创建设备节点

#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/ioctl.h> #define DRIVERNAME "xxx" #defi

spi驱动框架全面分析,从master驱动到设备驱动

内核版本:linux2.6.32.2 硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括: 1.I2C 驱动框架回顾 2.SPI 框架简单介绍 3.master 驱动框架 3.1 驱动侧 3.2 设备侧 4.SPI 设备驱动框架 4.1 设备册 4.2 驱动侧 5.设备驱动程序实例 1.I2C 驱动框架回顾 在前面学习 I2C 驱动程序的时候我们知道,I2C 驱动框架分为两层,一层是控制器驱动程序 i2c_adapter,它一般是由芯片厂商写好的,主要提供一个 algorithm