AM335X——SPI设备驱动

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/01/AM335X——SPI设备驱动/#more

最近在AM335X上写了几个SPI设备驱动,记录一下心得。

1. 准备工作

与前面写过的I2C驱动一样,SPI驱动也涉及SPI控制器(适配器)驱动SPI设备驱动
SPI控制器驱动这里不就写了,直接使用SDK自带的,只写SPI设备的驱动。
在写SPI设备驱动之前,需要先验证下SDK提供的SPI驱动是否能用,相关的设置是否正确,因此使用内核自带的一个SPI设备驱动进行测试。

1.1 驱动

首先配置内核,执行make menuconfig,勾选上McSPI driver for OMAPUser mode SPI device driver support

1.2 设备树

然后还要向设备树添加如下内容:
{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;
ti,pindir-d0-out-d1-in;

spidev@0 {
    spi-max-frequency = <25000000>;
    reg = <0>;
    compatible = "rohm,dh2228fv";

    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low */
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};
};
{% endcodeblock %}
这里有几个细节,简单说一下:
1.pinctrl-0属性引用的spi1_pins,指定了SPI的几个复用引脚,里面的0x190是真实地址0x990-0x800,也就是说设备树中的地址是相对0x800的偏移;
2.注意SPI复用引脚不要在设备树其它节点中使用,不然SPI驱动可能用不了;
3.AM335x的MOSI和MISO可以互换,需要加上ti,pindir-d0-out-d1-in;来指定D0是MOSI,D1是MISO;
4.reg = <0>;表示硬件片选,这里为硬件片选0;
5.内核自带的测试驱动程序的compatiblerohm,dh2228fv

1.3 测试程序

使用内核提供的测试程序,编译,测试。
测试文件路径:Documentation/spi/spidev_test.c
交叉编译后执行./spidev_test -D /dev/spidev1.0 -v
结果:

spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 KHz)
TX | FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F0 0D  | ......@....▒..................▒.
RX | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................................

此时可以通过逻辑分析仪或者短接MOSI和MISO,判断发出和接收的数据是否正常,从而验证SPI控制器驱动是否正常。

1.4 编译脚本

为了方便后续的编译,写了一个脚本进行操作,这个脚本也相当于操作流程,以供参考:
{% codeblock lang:sh [compiler_kernel.sh] %}
#!/bin/bash

#step 0:set env
export CPUS=grep -c processor /proc/cpuinfo
export ARCH=arm
export CROSS_COMPILE=/home/hceng/gcc-linaro-5.3-2016.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
export PATH=/home/hceng/gcc-linaro-5.3-2016.02-x86_64_arm-linux-gnueabihf/bin:$PATH

#step 1:clean kernel
#make distclean

#step 2:copy kernel config file
#make tisdk_am335x-evm_defconfig

#step 3:compiler kernel
#make uImage LOADADDR=0x10008000 -j C P U S m a k e z I m a g e − j {CPUS} make zImage -j CPUSmakezImagej{CPUS}

#step 4:compiler device tree
#make dtbs
make am335x-evm.dtb

#step 5:compiler driver module file(dynamic loading)
#make modules
#make modules_install INSTALL_MOD_PATH=~/rootfs/lib/modules/4.1.18-gbbe8cfc

#step 6:copy zImage and dtb to tftp download
rm /home/hceng/tftp/zImage
rm /home/hceng/tftp/am335x-evm.dtb

cp ./arch/arm/boot/zImage /home/hceng/tftp/
cp ./arch/arm/boot/dts/am335x-evm.dtb /home/hceng/tftp/
{% endcodeblock %}

其中第六步,拷贝倒tftp目录下,是为了方便板子启动的时候,通过U-Boot直接tftp下载编译过的内核和设备树,相关命令如下:

setenv ipaddr 192.168.1.14; setenv serverip 192.168.1.11; setenv gatewayip 192.168.1.1; setenv netmask 255.255.255.0; setenv fdtfile 'am335x-evm.dtb'; setenv rootpath '/home/hceng/rootfs';

setenv netargs "setenv bootargs console=${console} ${optargs}  root=/dev/nfs  rootfstype=nfsroot  nfsroot=${serverip}:${rootpath}  ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off"

setenv netboot "echo Booting from network ...; setenv autoload no; tftp ${fdtaddr} ${fdtfile}; tftp ${loadaddr} ${bootfile}; run netargs; bootz ${loadaddr} - ${fdtaddr}"

saveenv

run netboot

2. SPI设备——tlc5615

TLC5615是一个10位的DAC,最大输出电压位基准电压的两倍。
这个驱动比较简单,没什么特别的难点,
唯一特殊的是TLC5615每次传输是12位数据(10位data+2位extra),因此在probe()函数里,需要spi->bits_per_word = 12;
另外,因为每次传输的数据位12位,spi_write()的第三个参数不再是1,而是spi_write(spi_tlc5615_dev, &ker_buf, 2);

2.1 设备树

{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;

spidev@0 {
    spi-max-frequency = <25000000>;
    reg = <0>;
    compatible = "rohm,dh2228fv";

    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low       */
};

spidev@1 {
    spi-max-frequency = <25000000>;
    reg = <1>;
    compatible = "ti,tlc5615";
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};
};
{% endcodeblock %}

2.2 驱动程序

{% codeblock lang:c [tl5615.c] %}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

static int major;
static dev_t devid;
static struct class *tlc5615_class;
static struct cdev tlc5615_cdev;
static struct spi_device *spi_tlc5615_dev;

static int tlc5615_open (struct inode *node, struct file *filp)
{
return 0;
}

static int tlc5615_release (struct inode *node, struct file *filp)
{
return 0;
}

static ssize_t tlc5615_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{

int ret;
unsigned int ker_buf;

if(copy_from_user(&ker_buf, buf, 4))
   return 0;

if (ker_buf > 1023)
    ker_buf = 1023;

ker_buf = (ker_buf << 2) & (0xFFC);

//printk("ker_buf=%d\n", ker_buf);
ret = spi_write(spi_tlc5615_dev, &ker_buf, 2);
if(ret != 0)
{
    printk("spi write error\n");
    return -EINVAL;
}

return 4; 

}

static struct file_operations tlc5615_ops = {
.owner = THIS_MODULE,
.open = tlc5615_open,
.write = tlc5615_write,
.release = tlc5615_release,
};

static int tlc5615_probe(struct spi_device *spi)
{
int ret;

spi->bits_per_word = 12; //tl5615 transmits 12bits(10bits data + 2bit extra)each time.
if (spi_setup(spi) < 0)
{
    printk("spi master doesn't support 12 bits/word \n");
    return -EINVAL;
}   
    
spi_tlc5615_dev = spi;

if(alloc_chrdev_region(&devid, 0, 1, "tlc5615") < 0)
{
    printk(KERN_INFO"Unable to alloc_chrdev_region.\n");
    return -EINVAL;
}   
    
major = MAJOR(devid);
cdev_init(&tlc5615_cdev, &tlc5615_ops);        
ret = cdev_add(&tlc5615_cdev, devid, 1);
if (ret < 0)
{
    printk(KERN_ERR "Unable to cdev_add.\n");
    goto error;
}
    
tlc5615_class = class_create(THIS_MODULE, "tlc5615"); 

device_create(tlc5615_class, NULL, MKDEV(major, 0), NULL, "tlc5615"); // /dev/tlc5615

return 0;

error:
unregister_chrdev_region(devid, 1);

return -EINVAL;

}

static int tlc5615_remove(struct spi_device *spi)
{
device_destroy(tlc5615_class, MKDEV(major, 0));

class_destroy(tlc5615_class);

unregister_chrdev_region(devid, 1);
cdev_del(&tlc5615_cdev);

return 0;

}

static const struct of_device_id of_match_spi[] = {
{ .compatible = “ti,tlc5615”, .data = NULL },
{ /* sentinel */ }
};

static struct spi_driver tlc5615_driver = {
.probe = tlc5615_probe,
.remove = tlc5615_remove,
.driver = {
.name = “tlc5615”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
};

module_spi_driver(tlc5615_driver);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng [email protected]”);
MODULE_DESCRIPTION(“TI am335x board spi device: tl5615 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

2.3 测试程序

{% codeblock lang:c [tl5615_app.c] %}
#include
#include
#include
#include
#include
#include
#include

// ./tl5615_app val
// val = 0~4.096v (0~1023)

int main(int argc, char **argv)
{
int fd;
float f_val = atof(argv[1]);
unsigned int i_val = f_val * 1000 / 4;
if(i_val > 1023)
i_val = 1023;

fd = open("/dev/tlc5615", O_RDWR);   
if (fd < 0)
    printf("Can't open!\n");

write(fd, &i_val, 4);

close(fd);

return 0;

}
{% endcodeblock %}
输出电压计算公式:

output=2*(Vref)*(val/1024)  //其中val为SPI传输的前10位数据。

3. SPI设备——ssd1306

SSD1306是一个分辨率为128*64的OLED显示屏。
OLED的驱动稍微麻烦一点,除了对SSD1306的基本操作,还要解决以下两个问题。

  • 1.需要设备树提供DC引脚
    采用SPI接口的OLED,除了时钟引脚(CLK)、使能引脚(EN)、数据发送引脚(MOSI)外,还需要数据/命令切换引脚(DC)。
    因为OLED只接收数据的缘故,AM335X的数据接收引脚(MISO)就不需要了。
    因此需要在设备树中加入DC引脚信息,并在驱动中解析使用。

  • 2.需要支持软件片选
    AM335X的SPI只有两个硬件片选CS0和CS1,现在有了三个设备,两个片选自然是不够的,因此需要添加软件片选,即使用GPIO作为片选引脚。
    而AM335X的SDK目前是不支持软件片选的,因此需要修改SPI控制器驱动(spi-omap2-mcspi.c)来实现。

修改的思路也比较简单,关注两个变量**spi->chip_selectspi->cs_gpio**。
spi->chip_select是设备树中reg的值,为0、1分别表示CS0和CS1,从2开始,就意味着该设备使用GPIO作为片选了;
spi->cs_gpio是设备树中cs-gpios的值,如果在设备树中值为<0>,这里将为-2,说明使用的是硬件片选,如果在设备树中值为<&gpio0 13 0>,这里将为13,说明使用的是软件片选。

因此只需要在omap2_mcspi_setup()函数里,根据spi->chip_select的值来判断是否使用的是GPIO片选,如果不是,使用原来的函数,如果是,则对GPIO片选引脚进行初始化。
然后在omap2_mcspi_force_cs()函数里,根据spi->chip_select的值来判断是否使用的是GPIO片选,如果不是,使用原来的函数,如果是,则控制GPIO输出电平实现片选。

修改内容如下:
{% codeblock lang:diff %}
— spi-omap2-mcspi_bak.c 2018-12-27 16:06:05.159509054 +0800
+++ spi-omap2-mcspi.c 2018-12-28 11:25:42.242154915 +0800
@@ -35,7 +35,7 @@
#include

#include

+#include
#include

#define OMAP2_MCSPI_MAX_FREQ 48000000
@@ -245,14 +245,31 @@
static void omap2_mcspi_force_cs(struct spi_device *spi, int cs_active)
{
l = mcspi_cached_chconf0(spi);

  • if (spi->cs_gpio >= 0)
  • {
  •    if (cs_active)
    
  •    {
    
  •        l |= OMAP2_MCSPI_CHCONF_FORCE;
    
  •        mcspi_write_chconf0(spi, l);
    
  •        gpio_direction_output(spi->cs_gpio, 0);
    
  •    }
    
  •    else
    
  •    {
    
  •        l &= ~OMAP2_MCSPI_CHCONF_FORCE;
    
  •        mcspi_write_chconf0(spi, l);
    
  •        gpio_direction_output(spi->cs_gpio, 1);
    
  •    }
    
  • }
  • mcspi_write_chconf0(spi, l);
  • {
  •    l = mcspi_cached_chconf0(spi);
    
  •    if (cs_active)
    
  •        l |= OMAP2_MCSPI_CHCONF_FORCE;
    
  •    else
    
  •        l &= ~OMAP2_MCSPI_CHCONF_FORCE;
    
  •    mcspi_write_chconf0(spi, l);
    
  • }
    }

static void omap2_mcspi_set_master_mode(struct spi_master *master)
@@ -995,6 +1012,23 @@
struct omap2_mcspi_dma *mcspi_dma;
struct omap2_mcspi_cs *cs = spi->controller_state;

  • if (spi->chip_select > 1) //using GPIO as a chip select, reg value >1 in the devicetree

  • {

  •    if (spi->cs_gpio < 0)
    
  •        return -EIO;
    
  •    if (gpio_is_valid(spi->cs_gpio)) 
    
  •    {
    
  •        if ((gpio_request(spi->cs_gpio, "cs_gpio")) < 0)  
    
  •        {
    
  •            printk("Error requesting gpio %d for spi cs pin\n", spi->cs_gpio);
    
  •            return -EBUSY;
    
  •        }
    
  •        gpio_direction_output(spi->cs_gpio, 1);
    
  •    }
    
  •    spi->chip_select = 0;   //using GPIO as a chip select, only use DAM0/channel 0 or 1
    
  • }

  • mcspi_dma = &mcspi->dma_channels[spi->chip_select];

    if (!cs) {
    @@ -1056,6 +1090,9 @@
    mcspi_dma->dma_tx = NULL;
    }
    }

  • if (spi->cs_gpio >= 0)

  •    gpio_free(spi->cs_gpio);
    

}
{% endcodeblock %}

3.1 设备树

{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;

ti,pindir-d0-out-d1-in;
ti,spi-num-cs = <3>; 
cs-gpios = <0>, <0>, <&gpio0 13 0>;

spidev@0 {
    compatible = "rohm,dh2228fv";
    spi-max-frequency = <25000000>;
    reg = <0>;
    
    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low */
};

spidev@1 {
    compatible = "ti,tlc5615"; 
    spi-max-frequency = <25000000>;
    reg = <1>;
};

spidev@2 {
    compatible = "solomon,ssd1306fb-spi"; //OLED
    spi-max-frequency = <25000000>;
    reg = <2>;
    dc-gpio = <&gpio0 12 0>;
    pinctrl-0 = <&oled_dc_pin>;
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};

oled_dc_pin: oled_dc_pin0 {
    pinctrl-single,pins = <
        0x178 (PIN_INPUT_PULLDOWN | MUX_MODE7)  /* conf_uart1_ctsn.gpio0_12 */
    >;
};

};
{% endcodeblock %}
其中
ti,spi-num-cs指定片选数量;
cs-gpios列出所有的片选引脚,硬件片选填入0代替,软件片选填入对应的GPIO引脚;
spidev@2使用软件片选,reg因此要设置为2;
dc-gpio设置OLED的DC引脚;
pinctrl-0引用oled_dc_pin,将其设置为GPIO功能;

3.2 驱动程序

{% codeblock lang:c [fb_ssd1306_drv.c] %}
#include
#include
#include
#include
#include
#include
#include
#include

#include
#include
#include
#include

#include

#define OLED_CMD_INIT 0x100001
#define OLED_CMD_CLEAR_ALL 0x100002
#define OLED_CMD_CLEAR_PAGE 0x100003
#define OLED_CMD_SET_POS 0x100004

static int major;
static dev_t devid;
static struct class *ssd1306_class;
static struct cdev ssd1306_cdev;

static unsigned char *ker_buf;

static int spi_oled_dc_pin;
static struct spi_device *spi_ssd1306_dev;

static void ssd1306_set_dc(char val)
{
gpio_direction_output(spi_oled_dc_pin, val);
}

static void ssd1306_write_cmd(unsigned char cmd)
{
ssd1306_set_dc(0);
spi_write(spi_ssd1306_dev, &cmd, 1);
ssd1306_set_dc(1);
}

static void ssd1306_write_data(unsigned char data)
{
ssd1306_set_dc(1);
spi_write(spi_ssd1306_dev, &data, 1);
ssd1306_set_dc(1);
}

static void ssd1306_set_page_addr_mode(void)
{
ssd1306_write_cmd(0x20);
ssd1306_write_cmd(0x02);
}

static void ssd1306_set_pos(int page, int col)
{
ssd1306_write_cmd(0xB0 + page); //page address

ssd1306_write_cmd(col & 0xF);          //Lower Column Start Address 
ssd1306_write_cmd(0x10 + (col >> 4));  //Lower Higher Start Address 

}

static void ssd1306_clear(void)
{
int page, i;
for (page = 0; page < 8; page ++)
{
ssd1306_set_pos(page, 0);
for (i = 0; i < 128; i++)
ssd1306_write_data(0);
}
}

void ssd1306_clear_page(int page)
{
int i;
ssd1306_set_pos(page, 0);
for (i = 0; i < 128; i++)
ssd1306_write_data(0);
}

void ssd1306_init(void)
{
ssd1306_write_cmd(0xAE); /display off/
ssd1306_write_cmd(0x00); /set lower column address/
ssd1306_write_cmd(0x10); /set higher column address/
ssd1306_write_cmd(0x40); /set display start line/
ssd1306_write_cmd(0xB0); /set page address/
ssd1306_write_cmd(0x81); /contract control/
ssd1306_write_cmd(0x66); /128/
ssd1306_write_cmd(0xA1); /set segment remap/
ssd1306_write_cmd(0xA6); /normal / reverse/
ssd1306_write_cmd(0xA8); /multiplex ratio/
ssd1306_write_cmd(0x3F); /duty = 1/64/
ssd1306_write_cmd(0xC8); /Com scan direction/
ssd1306_write_cmd(0xD3); /set display offset/
ssd1306_write_cmd(0x00);
ssd1306_write_cmd(0xD5); /set osc division/
ssd1306_write_cmd(0x80);
ssd1306_write_cmd(0xD9); /set pre-charge period/
ssd1306_write_cmd(0x1f);
ssd1306_write_cmd(0xDA); /set COM pins/
ssd1306_write_cmd(0x12);
ssd1306_write_cmd(0xdb); /set vcomh/
ssd1306_write_cmd(0x30);
ssd1306_write_cmd(0x8d); /set charge pump enable/
ssd1306_write_cmd(0x14);

ssd1306_set_page_addr_mode();

ssd1306_clear();

ssd1306_write_cmd(0xAF); /*display ON*/    

}

static long ssd1306_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int page;
int col;

switch (cmd)
{
    case OLED_CMD_INIT:
    {
        ssd1306_init();
        break;
    }
    case OLED_CMD_CLEAR_ALL:
    {
       ssd1306_clear();
        break;
    }
    case OLED_CMD_CLEAR_PAGE:
    {
        page = arg;
        ssd1306_clear_page(page);
        break;
    }
    case OLED_CMD_SET_POS:
    {
        page = arg & 0xff;
        col  = (arg >> 8) & 0xff;
        ssd1306_set_pos(page, col);
        break;
    }
}

return 0;

}

static ssize_t ssd1306_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);
ssd1306_set_dc(1); /* data */
spi_write(spi_ssd1306_dev, ker_buf, count);

return 0;

}

static struct file_operations ssd1306_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ssd1306_ioctl,
.write = ssd1306_write,
};

static int ssd1306_probe(struct spi_device *spi)
{
int ret;

spi_ssd1306_dev = spi;

spi_oled_dc_pin = of_get_named_gpio(spi_ssd1306_dev->dev.of_node, "dc-gpio", 0); //dc and uart1 is same pin.

if (gpio_is_valid(spi_oled_dc_pin)) 
{
    if ((gpio_request(spi_oled_dc_pin, "dc_pin")) < 0)  
    {
        printk(KERN_ERR "Error requesting gpio %d for spi oled dc pin\n", spi_oled_dc_pin);
        return -1;
    }
    
    gpio_direction_output(spi_oled_dc_pin, 0);
}

ker_buf = kmalloc(4096, GFP_KERNEL);
if (!ker_buf)
{
    printk(KERN_ERR "kmalloc error\n");
    goto err1;
}

if(alloc_chrdev_region(&devid, 0, 1, "ssd1306_dev") < 0)
{
    printk(KERN_INFO"Unable to alloc_chrdev_region.\n");
    goto err2;
}   

major = MAJOR(devid);
cdev_init(&ssd1306_cdev, &ssd1306_ops);        
ret = cdev_add(&ssd1306_cdev, devid, 1);
if (ret < 0)
{
    printk(KERN_ERR "Unable to cdev_add.\n");
    goto err3;
}
    
ssd1306_class = class_create(THIS_MODULE, "ssd1306_class"); 

device_create(ssd1306_class, NULL, MKDEV(major, 0), NULL, "ssd1306"); // /dev/ssd1306

return 0;

err3:
unregister_chrdev_region(devid, 1);
cdev_del(&ssd1306_cdev);

err2:
kfree(ker_buf);

err1:
gpio_free(spi_oled_dc_pin);

return -1;

}

static int ssd1306_remove(struct spi_device *spi)
{
device_destroy(ssd1306_class, MKDEV(major, 0));

class_destroy(ssd1306_class);

unregister_chrdev_region(devid, 1);
cdev_del(&ssd1306_cdev);

kfree(ker_buf);

gpio_free(spi_oled_dc_pin);

return 0;

}

static const struct of_device_id of_match_spi[] = {
{ .compatible = “solomon,ssd1306fb-spi”, .data = NULL },
{ /* sentinel */ }
};

static struct spi_driver ssd1306_driver = {
.probe = ssd1306_probe,
.remove = ssd1306_remove,
.driver = {
.name = “ssd1306_drv”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
};

module_spi_driver(ssd1306_driver);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng [email protected]”);
MODULE_DESCRIPTION(“TI am335x board spi device: ssd1306 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

SPI设备驱动程序没什么特别的。

3.3 测试程序

{% codeblock lang:c [oled_app.c] %}
#include
#include
#include
#include
#include
#include
#include
#include

/* oled_test init

  • oled_test clear
  • oled_test clear
  • oled_test
    */

#define OLED_CMD_INIT 0x100001
#define OLED_CMD_CLEAR_ALL 0x100002
#define OLED_CMD_CLEAR_PAGE 0x100003
#define OLED_CMD_SET_POS 0x100004

const unsigned char oled_asc2_8x16[95][16]=
{
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},// 0
{0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00},//!1
{0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//"2
{0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00},//#3
{0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00},//$4
{0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00},//%5
{0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10},//&6
{0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//'7
{0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},//(8
{0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},//)9
{0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},//*10
{0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00},//+11
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00},//,12
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01},//-13
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},//.14
{0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00},///15
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},//016
{0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//117
{0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},//218
{0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00},//319
{0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00},//420
{0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00},//521
{0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00},//622
{0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00},//723
{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},//824
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00},//925
{0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},//:26
{0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00},//;27
{0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},//<28
{0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00},//=29
{0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},//>30
{0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00},//?31
{0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00},//@32
{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},//A33
{0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},//B34
{0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},//C35
{0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},//D36
{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},//E37
{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},//F38
{0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},//G39
{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},//H40
{0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//I41
{0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},//J42
{0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},//K43
{0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},//L44
{0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00},//M45
{0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},//N46
{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},//O47
{0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},//P48
{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00},//Q49
{0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},//R50
{0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},//S51
{0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//T52
{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//U53
{0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},//V54
{0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00},//W55
{0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},//X56
{0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//Y57
{0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},//Z58
{0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},//[59
{0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},//\60
{0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},//]61
{0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//^62
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},//_63
{0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//`64
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20},//a65
{0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},//b66
{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},//c67
{0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20},//d68
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00},//e69
{0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//f70
{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},//g71
{0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//h72
{0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//i73
{0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},//j74
{0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00},//k75
{0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//l76
{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},//m77
{0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//n78
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//o79
{0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00},//p80
{0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80},//q81
{0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},//r82
{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},//s83
{0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00},//t84
{0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},//u85
{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00},//v86
{0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00},//w87
{0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00},//x88
{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00},//y89
{0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},//z90
{0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40},//{91
{0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},//|92
{0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00},//}93
{0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//~94
};

/* page: 0-7

  • col : 0-127

  • 字符: 8x16象素
    /
    void OLEDPutChar(int fd, int page, int col, char c)
    {
    int i = 0;
    /
    得到字模 */
    const unsigned char *dots = oled_asc2_8x16[c - ’ '];

    /* 发给OLED /
    //OLEDSetPos(page, col);
    //ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
    ioctl(fd, OLED_CMD_SET_POS, page | (col << 8));
    /
    发出8字节数据 */
    //for (i = 0; i < 8; i++)
    // OLEDWriteDat(dots[i]);
    write(fd, &dots[0], 8);

    //OLEDSetPos(page+1, col);
    //ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
    ioctl(fd, OLED_CMD_SET_POS, (page+1) | (col << 8));
    /* 发出8字节数据 */
    //for (i = 0; i < 8; i++)
    // OLEDWriteDat(dots[i+8]);
    write(fd, &dots[8], 8);
    }

/* page: 0-7

  • col : 0-127

  • 字符: 8x16象素
    */
    void OLEDPrint(int fd, int page, int col, char *str)
    {
    int i = 0;

    ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
    ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
    while (str[i])
    {
    OLEDPutChar(fd, page, col, str[i]);
    col += 8;
    if (col > 127)
    {
    col = 0;
    page += 2;
    ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
    ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
    }
    i++;
    }
    }

void print_usage(char *cmd)
{
printf(“Usage:\n”);
printf("%s init\n", cmd);
printf("%s clear\n", cmd);
printf("%s clear \n", cmd);
printf("%s \n", cmd);
printf(“eg:\n”);
printf("%s 2 0 100ask.taobao.com\n", cmd);
printf(“page is 0,1,…,7\n”);
printf(“col is 0,1,…,127\n”);
}

int main(int argc, char **argv)
{
int do_init = 0;
int do_clear = 0;
int do_show = 0;
int page = -1;
int col;

int fd;

if (argc == 2 && !strcmp(argv[1], "init"))
    do_init = 1;
if ((argc == 2) && !strcmp(argv[1], "clear"))
{
    do_clear = 1;
}
if ((argc == 3) && !strcmp(argv[1], "clear"))
{
    do_clear = 1;
    page = strtoul(argv[2], NULL, 0);
}
if (argc == 4)
{
    do_show = 1;
    page = strtoul(argv[1], NULL, 0);
    col = strtoul(argv[2], NULL, 0);
}

if (!do_init && !do_clear && !do_show)
{
    print_usage(argv[0]);
    return -1;
}

fd = open("/dev/ssd1306", O_RDWR);
if (fd < 0)
{
    printf("can't open /dev/ssd1306\n");
    return -1;
}

if (do_init)
    ioctl(fd, OLED_CMD_INIT);
else if (do_clear)
{
    if (page == -1)
        ioctl(fd, OLED_CMD_CLEAR_ALL);
    else
    {
        if (page < 0 || page > 7)
        {
            printf("page is 0,1,...,7\n");
            return -1;
        }
        ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
    }
}
else if (do_show)
{
    if (page < 0 || page > 7)
    {
        printf("page is 0,1,...,7\n");
        return -1;
    }
    if (col < 0 || col > 127)
    {
        printf("col is 0,1,...,127\n");
        return -1;
    }

    OLEDPrint(fd, page, col, argv[3]);
}
return 0;

}
{% endcodeblock %}

4. SPI设备——w25q16dv

w25q16dv是一个2M大小的Flash。

在写驱动之前,需要了解下这几个知识点:

  • 1.w25q16dvBytePageSectorBlock之间的关系
    Byte(字节)由8个bits(位)组成,是数据的最小存储单位,1024个Byte就是我们常见的1KB;
    Page(页)由256个Byte组成,w25q16dv每次SPI写操作只能是1~256个字节,因此遇到大数据传输时,需要分页写;
    Sector(扇区)由16个Byte组成,也是4K大小,w25q16dv每次SPI擦除操作就是以Sector为最小单位;
    Block(块)由16个Sector组成,为64K大小;
    它们之间大小关系如下:
另外,Flash的硬件决定了,每个`bits`只能从1变为0,不能0变成1,因此每次写之前需要先擦除为`0xFF`,再写入数据。
  • 2.DMA与SPI的关系
    AM335X的SPI控制器的源码里,会根据SPI一次传输的数据长短来决定是否启用DMA传输。
    当一次传输的数据长度小于DMA_MIN_BYTES,则不会启用DMA,CPU会先读取RAM中的数据,再写到SPI控制器的TX_BUF寄存器里面,如下图路线①;
    当一次传输的数据长度大于DMA_MIN_BYTES,则会启用DMA,CPU设置好DMA后,DMA自动从RAM搬运数据到SPI控制器的TX_BUF寄存器里面,如下图路线②;
  • 3.函数调用关系分析
    在应用层写操作w25q16dv时,会调用到w25q16dv_drv.c
w25q16dv_write();
    spi_flash_program();
        spi_sync();

再调用到drivers/spi/spi.c:

__spi_sync();
    __spi_pump_messages();
        master->transfer_one_message(master, master->cur_msg);	

再调用到drivers/spi/spi-omap2-mcspi.c:

omap2_mcspi_transfer_one_message();
    dma_map_single(); //根据长度是否映射DMA
    omap2_mcspi_work();
        omap2_mcspi_txrx_dma(); //使用DMA传输
        或
        omap2_mcspi_txrx_pio(); //使用PIO传输		

4.1 设备树

{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;

ti,pindir-d0-out-d1-in;
ti,spi-num-cs = <4>; 
cs-gpios = <0>, <0>, <&gpio0 13 0>, <&gpio0 14 0>;

spidev@0 {
    compatible = "rohm,dh2228fv";
    spi-max-frequency = <25000000>;
    reg = <0>;
    
    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low */
};

spidev@1 {
    compatible = "ti,tlc5615"; 
    spi-max-frequency = <25000000>;
    reg = <1>;
};

spidev@2 {
    compatible = "solomon,ssd1306fb-spi"; //OLED
    spi-max-frequency = <25000000>;
    reg = <2>;
    dc-gpio = <&gpio0 12 0>;
    pinctrl-0 = <&oled_dc_pin>;
};

spidev@3 {               
    spi-max-frequency = <20000000>;
    reg = <3>;
    compatible = "winbond,w25q16dv";
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};

oled_dc_pin: oled_dc_pin0 {
    pinctrl-single,pins = <
        0x178 (PIN_INPUT_PULLDOWN | MUX_MODE7)  /* conf_uart1_ctsn.gpio0_12 */
    >;
};

};
{% endcodeblock %}

4.2 程序驱动

{% codeblock lang:c [w25q16dv_drv.c] %}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include
#include
#include

static struct spi_device *spi_w25q16dv_dev;
static struct mtd_info flash_w25q16dv_dev;

void spi_flash_read_ID(int *pMID, int *pDID)
{
unsigned char tx_buf[4];
unsigned char rx_buf[2];

tx_buf[0] = 0x90;
tx_buf[1] = 0;
tx_buf[2] = 0;
tx_buf[3] = 0;

spi_write_then_read(spi_w25q16dv_dev, tx_buf, 4, rx_buf, 2);

*pMID = rx_buf[0];
*pDID = rx_buf[1];

}

static void spi_flash_write_eable(int enable)
{
unsigned char val = enable ? 0x06 : 0x04;
spi_write(spi_w25q16dv_dev, &val, 1);
}

static unsigned char spi_flash_read_status_reg1(void)
{
unsigned char val;
unsigned char cmd = 0x05;

spi_write_then_read(spi_w25q16dv_dev, &cmd, 1, &val, 1);

return val;

}

static unsigned char spi_flash_read_status_reg2(void)
{
unsigned char val;
unsigned char cmd = 0x35;

spi_write_then_read(spi_w25q16dv_dev, &cmd, 1, &val, 1);

return val;

}

static void spi_flash_wait_when_busy(void)
{
while (spi_flash_read_status_reg1() & 1)
{
/* Sector erase time : 60ms
* Page program time : 0.7ms
* Write status reg time : 10ms
*/
set_current_state(TASK_INTERRUPTIBLE); //Sleep for a while
schedule_timeout(HZ/100); //Judging again after sleeping 10MS(1s=1HZ)
}
}

static void spi_flash_write_status_reg(unsigned char reg1, unsigned char reg2)
{
unsigned char tx_buf[3];

spi_flash_write_eable(1);  

tx_buf[0] = 0x01;
tx_buf[1] = reg1;
tx_buf[2] = reg2;

spi_write(spi_w25q16dv_dev, tx_buf, 3);   

spi_flash_wait_when_busy();

}

static void spi_flash_clear_protect_for_status_reg(void)
{
unsigned char reg1, reg2;

reg1 = spi_flash_read_status_reg1();
reg2 = spi_flash_read_status_reg2();

reg1 &= ~(1<<7);
reg2 &= ~(1<<0);

spi_flash_write_status_reg(reg1, reg2);

}

static void spi_flash_clear_protect_for_data(void)
{
/* cmp=0,bp2,1,0=0b000 */
unsigned char reg1, reg2;

reg1 = spi_flash_read_status_reg1();
reg2 = spi_flash_read_status_reg2();

reg1 &= ~(7<<2);
reg2 &= ~(1<<6);

spi_flash_write_status_reg(reg1, reg2);

}

/* erase 4K */
void spi_flash_erase_sector(unsigned int addr)
{
unsigned char tx_buf[4];
tx_buf[0] = 0x20;
tx_buf[1] = addr >> 16;
tx_buf[2] = addr >> 8;
tx_buf[3] = addr & 0xff;

spi_flash_write_eable(1);  

spi_write(spi_w25q16dv_dev, tx_buf, 4);

spi_flash_wait_when_busy();

}

/* program */
void spi_flash_program(unsigned int addr, unsigned char *buf, int len)
{
int ret;
int i;
unsigned char tx_buf[4];
struct spi_transfer t[] = {
{
.tx_buf = tx_buf,
.len = 4,
},
{
.tx_buf = buf,
.len = len,
},
};
struct spi_message m;

tx_buf[0] = 0x02;
tx_buf[1] = addr >> 16;
tx_buf[2] = addr >> 8;
tx_buf[3] = addr & 0xff;


spi_flash_write_eable(1);  

spi_message_init(&m);
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);
ret = spi_sync(spi_w25q16dv_dev, &m);
if (ret)
{
    printk("spi_flash_program spi_syn err : %d\n", ret);
}

spi_flash_wait_when_busy();    

}

void spi_flash_read(unsigned int addr, unsigned char *buf, int len)
{
unsigned char tx_buf[4];
struct spi_transfer t[] = {
{
.tx_buf = tx_buf,
.len = 4,
},
{
.rx_buf = buf,
.len = len,
},
};
struct spi_message m;

tx_buf[0] = 0x03;
tx_buf[1] = addr >> 16;
tx_buf[2] = addr >> 8;
tx_buf[3] = addr & 0xff;

spi_message_init(&m);
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);
spi_sync(spi_w25q16dv_dev, &m); 

}

static void spi_flash_init(void)
{
spi_flash_clear_protect_for_status_reg();
spi_flash_clear_protect_for_data();
}

static int w25q16dv_erase(struct mtd_info *mtd, struct erase_info *instr)
{
unsigned int addr = instr->addr;
unsigned int len = 0;

//Judgment parameter
if ((addr & (flash_w25q16dv_dev.erasesize - 1)) || (instr->len & (flash_w25q16dv_dev.erasesize - 1)))
{
    printk("w25q16dv_erase:addr/len is not aligned\n");
    return -EINVAL;
}

for (len = 0; len < instr->len; len += 4096)
{
    spi_flash_erase_sector(addr);
    addr += 4096;
}

instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);

return 0;

}

#define NUM 256
static int w25q16dv_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
{
int i;
int left = len;
int rlen;

#if 0
spi_flash_read(from, buf, len);
#else
while (left > 0)
{
if (left <= NUM)
rlen = left;
else
rlen = NUM;
spi_flash_read(from, buf, rlen);
from += rlen;
buf += rlen;
left -= rlen;
}
#endif
*retlen = len;
return 0;
}

static int w25q16dv_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
{
#if 0
int data_addr = to;
int data_len = len;
int i=0, left_len=0, right_len=0, page_num=0;

printk("data_addr=0x%02x, data_len=0x%x\n", data_addr, data_len);
for (i = 0; i < 16 && i < data_len; i++)
{
    printk("%02x ", buf[i]);
}
printk("\n");
       
if (data_addr%NUM + data_len <= NUM)
{
    spi_flash_program(data_addr, (unsigned char *)buf, data_len); 
}
    
else
{
    left_len = NUM - (data_addr%NUM);
    page_num = (data_len - left_len) / NUM;
    right_len = len - page_num*NUM - left_len;

    spi_flash_program(data_addr, (unsigned char *)buf, left_len); 
    for (i=0; i

#else

unsigned int addr = to;
int unalign = addr & (NUM - 1);
int wlen;
int left = len;
int i;

if (unalign)
{
    wlen = NUM - unalign;
    if (wlen >= len) 
        wlen = len;
    spi_flash_program(addr, (unsigned char *)buf, wlen); 
    addr += wlen;
    buf += wlen;
    left -= wlen;
}

while (left > 0)
{
    if (left >= NUM)
        wlen = NUM;
    else
        wlen = left;
    
    spi_flash_program(addr, (unsigned char *)buf, wlen);

    addr += wlen;
    buf += wlen;
    left -= wlen;
}

*retlen = len;

#endif

return 0;

}

static int w25q16dv_probe(struct spi_device *spi)
{
int mid, did;

printk(KERN_INFO "run w25q16dv_probe\n");

spi_w25q16dv_dev = spi;

spi_flash_init();
spi_flash_read_ID(&mid, &did);
printk("SPI Flash ID: %02x %02x\n", mid, did);
memset(&flash_w25q16dv_dev, 0, sizeof(flash_w25q16dv_dev)); 

/* Setup the MTD structure */
flash_w25q16dv_dev.name = "w25q16dv_spi_flash";
flash_w25q16dv_dev.type = MTD_NORFLASH;
flash_w25q16dv_dev.flags = MTD_CAP_NORFLASH;
flash_w25q16dv_dev.size = 0x200000;  /* 2M */
flash_w25q16dv_dev.writesize = 1; 
flash_w25q16dv_dev.writebufsize = 4096; /* no use */
flash_w25q16dv_dev.erasesize = 4096;    /* Minimum unit of erasure */

flash_w25q16dv_dev.owner = THIS_MODULE;
flash_w25q16dv_dev._erase = w25q16dv_erase;
flash_w25q16dv_dev._read  = w25q16dv_read;
flash_w25q16dv_dev._write = w25q16dv_write;

mtd_device_register(&flash_w25q16dv_dev, NULL, 0);

return 0;

}

static int w25q16dv_remove(struct spi_device *spi)
{
printk(KERN_INFO “run w25q16dv_remove\n”);

mtd_device_unregister(&flash_w25q16dv_dev);

return 0;

}

static const struct of_device_id of_match_spi[] = {
{ .compatible = “winbond,w25q16dv”, .data = NULL },
{ /* sentinel */ }
};

static struct spi_driver w25q16dv_driver = {
.probe = w25q16dv_probe,
.remove = w25q16dv_remove,
.driver = {
.name = “w25q16dv_drv”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
};

module_spi_driver(w25q16dv_driver);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng [email protected]”);
MODULE_DESCRIPTION(“TI am335x board spi device: w25q16dv driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}
这是遇到过数不多的块设备驱动,块设备驱动的写法都要忘了,以后块设备驱动可以参考这个。

4.3 测试程序

在加载w25q16dv.ko驱动后,将会生成两个设备节点:/dev/mtdx/dev/mtdblockx
/dev/mtdx是字符设备的节点,可以像字符设备那样读写;
/dev/mtdblockx是块设备的节点,可以进行块设备类似的操作;
两个节点都是操作的同一设备,而且最后调用的读写函数也是驱动中的同一个;
两者差异体现在进行字符设备读写时,可以直接写到某个地址,而对块设备而言,需要先读块的内容,再擦除一个块,再写一个块。

测试方法有两种,
一种是自己写应用程序,通过open()read()write()等进行操作;
另一种是把整个Flash格式化成各种文件系统格式,然后挂载,拷贝文件,卸载,再次挂载检查之前拷贝的文件是否正常。

  • 应用程序读写测试:
    {% codeblock lang:c [spi_flash_app.c] %}
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

#include

// Data types
#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned long
#define u64 unsigned long long int

#define s8 char
#define s16 int
#define s32 long int
#define s64 long long int
#define boolean unsigned char

#define BUFFER_SIZE 1024
#define OTP_NUM_WORDS 0x40

#define SUCCESS 0
#define FAIL -1
#define TRUE 1
#define FALSE 0

static const char *const short_options = “ho:d:r:w::e”;

static const struct option long_options[] =
{

{"help",   no_argument,       NULL, 'h'},
{"device", required_argument, NULL, 'd'},
{"read",   required_argument, NULL, 'r'},
{"offset", required_argument, NULL, 'o'},
{"write",  optional_argument, NULL, 'w'},
{"erase",  no_argument,       NULL, 'e'},
{0, 0, 0, 0}

};

typedef enum
{
RUN_READ,
RUN_WRITE,
RUN_ERASE,
RUN_END
} run_type;

static struct mtd_info_user info;

static void show_usage()
{
printf("\nUsage: ./spi_flash_app -d \n");
printf("\nOperation:\n");
printf("\t-e\n");
printf("\t-r\n");
printf("\t-w\n");
printf("\t-o \n");

exit(0);

}

static int mtd_read(const char *device, const unsigned int offset, const unsigned int size)
{
assert(device);
assert(size > 0);

int fd, ret = -1;
fd = open(device, O_RDONLY);
if(fd < 0)
{
    printf("(E) Open device %s failed.\n", device);
    return -1;
}

if (-1 == lseek(fd, offset, SEEK_SET))
{
    printf("lseek 0x%x error\n");
    return -1;
}

char buf[size];
memset(buf, 0, size);
ret = read(fd, buf, size);
if(ret != -1)
{
    printf("buffer_read: \n");
    int i;
    for(i = 0; i < ret; i++)
    {
        printf("%02x ", buf[i]);
        if(!((i + 1) % 10)) printf("\n");
    }
    printf("\n Read ok!\n");
}
else
{
    printf("(E) Read error!\n");
}

close(fd);
return ret;

}

static int mtd_write(const char *device, const void *data, const unsigned int offset, const unsigned int size)
{
assert(device);
assert(data);
assert(size > 0);

int fd, ret = -1;
fd = open(device, O_RDWR);
if(fd < 0)
{
    printf("(E) Open device %s failed.\n", device);
    return -1;
}

if (-1 == lseek(fd, offset, SEEK_SET))
{
    printf("lseek 0x%x error\n");
    return -1;
}

ret = write(fd, data, size);
if(ret != -1)
{
    if( ret == 0)
        printf("\n Nothing to write!\n");
    else
        printf("\n Write ok!\n");
}
else
{
    printf("(E) Write error!\n");
}

close(fd);
return ret;

}

static int non_region_erase(const int fd, const int start, int count, const int unlock)
{

mtd_info_t meminfo;

if(ioctl(fd, MEMGETINFO, &meminfo) == 0)
{
    erase_info_t erase;
    erase.start = start;
    erase.length = meminfo.erasesize;

    for(; count > 0; count--)
    {
        printf("Performing Flash Erase of length %u at offset 0x%x\n", erase.length, erase.start);
        fflush(stdout);
        if(unlock != 0)
        {
            printf("Performing Flash unlock at offset 0x%x\n", erase.start);
            if(ioctl(fd, MEMUNLOCK, &erase) != 0)
            {
                perror("MTD unlock failure");
                close(fd);
                return -1;
            }
        }
        if(ioctl(fd, MEMERASE, &erase) != 0)
        {
            perror("MTD erase failure");
            close(fd);
            return -1;
        }
        erase.start += meminfo.erasesize;
    }
    printf("  done\n");
}
return 0;

}

static int mtd_erase(const char *device, const unsigned int regcount)
{
assert(device);

int fd, ret = -1;
fd = open(device, O_RDWR);
if(fd < 0)
{
    printf("(E) Open device %s failed.\n", device);
    return -1;
}

if(regcount == 0)
{
    ret = non_region_erase(fd, 0, (info.size / info.erasesize), 0);
    if(ret == 0)
    {
        printf("\n Erase ok!\n");
    }
    else
    {
        printf("(E) Erase error!\n");
    }
}

close(fd);
return ret;

}

static int EeReadLineFromEepFile(FILE *DataFile, s8 *Buffer, const u32 BufferSize)
{
u32 Sign = 0;
u16 StringIndex = 0;
s16 EeStatus = SUCCESS;
boolean EndOfLine = FALSE;
boolean Comment = FALSE;

do
{
    /* Read line from the file. If the line is longer
     * it will be serviced below */
    if(fgets(Buffer, BufferSize, DataFile) == NULL)
    {
        EeStatus = FAIL;
        break;
    }

    /* Look for:
     * a semicolon in the string indicating a comment
     * a EOL just for detection if this is a full line
     * read to the buffer */
    for(StringIndex = 0; StringIndex < BufferSize; StringIndex++)
    {
        /* Break the loop if string has ended */
        if(Buffer[StringIndex] == '\0')
        {
            break;
        }

        /* Detect end of line and comments */
        if(Buffer[StringIndex] == '\n' ||
                Buffer[StringIndex] == '\r' ||
                Buffer[StringIndex] == ';')
        {
            /* Determine which we encountered...EOL or
            * comment...add terminating NULL as needed */
            if(Buffer[StringIndex] == ';')
            {
                Comment = TRUE;
                Buffer[StringIndex] = '\0';
            }
            else
            {
                EndOfLine = TRUE;
            }
        }
    }

    /* If the end of line was not detected - read the rest of
     * line and omit it if it's a comment */
    if(EndOfLine == FALSE)
    {
        if(Comment == TRUE)
        {
            while((Sign = getc(DataFile)) != EOF)
            {
                if(Sign == (u32)'\n' || Sign == (u32)'\r')
                {
                    break;
                }
            }
        }
        /* If data in buffer does not contain the whole line
         * (it does not contain comment)
         * then it will be read in next step */
    }

    /* If this line is empty, clear local flags */
    if(*Buffer == '\0')
    {
        EndOfLine =  FALSE;
        Comment = FALSE;
    }
}
while(*Buffer == '\0');

return EeStatus;

}

static int mtd_write_file(const char *device, const char *filename)
{
s8 retval = FAIL;
u16 i = 0;
s16 maxlen = 8 * 64;
FILE *file = NULL;
u32 b[8];
s8 temp[200];
u16 buffer[OTP_NUM_WORDS * 8];

file = fopen(filename, "r");
if(NULL == file)
{
    printf("Unable to open specified file: %s\n", filename);
    return -1;
}
else
{
    //FILE *file_ro = file; // WTF! (FILE *)0x12008 --> (FILE *)0x10000 by sscanf
    memset(b, 0x0, sizeof(b));
    memset(buffer, 0x0, sizeof(buffer));

    retval = EeReadLineFromEepFile(file, temp, 200);

    while ((SUCCESS == retval) && (i < maxlen))
    {
        // place the hex numbers from the line read in to the temp buffer
        sscanf(temp, "%08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx", \
               & (b[0]), &(b[1]), &(b[2]), &(b[3]), \
               & (b[4]), &(b[5]), &(b[6]), &(b[7]));

        // copy the words to the permanent buffer
        buffer[i + 0] = b[0];
        buffer[i + 1] = b[1];
        buffer[i + 2] = b[2];
        buffer[i + 3] = b[3];
        buffer[i + 4] = b[4];
        buffer[i + 5] = b[5];
        buffer[i + 6] = b[6];
        buffer[i + 7] = b[7];

        // increment i by 8 so we can get the next 8 words
        i += 8;

        // read the next line
        retval = EeReadLineFromEepFile(file, temp, 200);
    }
}
fclose(file);
mtd_write(device, buffer, 0, sizeof(buffer));

return 0;

}

int main(int argc, char *argv[])
{
int fd;
int i;
char *device = NULL;
int regcount;
run_type mode = -1;
int read_size = 0;
int write_size = 0;
char *opt_file = NULL;
unsigned int offset = 0;

int c;
while((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1)
{

    switch(c)
    {

    case 'h':
        show_usage();
        break;
    case 'd':
        device = optarg;
        break;
    case 'r':
    {
        mode = RUN_READ;
        read_size = atoi(optarg);
        printf("read_size=%d\n", read_size);
    }
    break;
    case 'o':
    {
        offset = strtoul(optarg, NULL, 0);
        printf("offset= 0x%x\n", offset);
    }
    break;
    case 'w':
    {
        mode = RUN_WRITE;
        opt_file = optarg;
        printf("opt_file : %s\n", opt_file);
    }
    break;
    case 'e':
        mode = RUN_ERASE;
        break;
    default:
        break;
    }
}

if(device == NULL)
{
    printf("(E) Device is required, please check it.\n");
    show_usage();
}
printf("device = %s\n", device);

fd = open(device, O_RDWR);
if(fd < 0)
{
    printf("open device %s error\n", device);
    return -1;
}

if(ioctl(fd, MEMGETINFO, &info) == 0)
{
    printf("info.size=%d\n", info.size);
    printf("info.erasesize=%d\n", info.erasesize);
    printf("info.writesize=%d\n", info.writesize);
    printf("info.oobsize=%d\n", info.oobsize);
}

if(ioctl(fd, MEMGETREGIONCOUNT, ®count) == 0)
    printf("regcount = %d\n", regcount);

close(fd);

if(RUN_READ == mode)
    mtd_read(device, offset, read_size);
else if(RUN_WRITE == mode)
{
    if(opt_file)
    {
        printf("Write opt_file to device %s\n", device);
        mtd_write_file(device, opt_file);
    }
    else
    {
        printf("Write buf[10] to device %s\n", device);
        char buf[10] =
        {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
        };
        mtd_write(device, buf, offset, sizeof(buf));
    }
}
else if(RUN_ERASE == mode)
    mtd_erase(device, regcount);

return 0;

}
{% endcodeblock %}

使用方法:

./flash_test -d /dev/mtd10 -e //擦除
./flash_test -d /dev/mtd10 -r 10 -o 0  //从地址偏移0读出10字节
./flash_test -d /dev/mtd10 -w -o 0     //从地址偏移0写10字节
./flash_test -d /dev/mtd10 -r 10 -o 0

./flash_test -d /dev/mtd10 -e  
./flash_test -d /dev/mtdblock10 -r 10 -o 0   
./flash_test -d /dev/mtdblock10 -w -o 0  
./flash_test -d /dev/mtdblock10 -r 10 -o 0   

另外,部分根文件系统集成了flash测试工具,但只能擦除操作:

flash_eraseall /dev/mtd10
  • 当作文件系统测试:
mkfs.vfat -I /dev/mtdblock10
mount -t vfat /dev/mtdblock10 /hceng

cp /etc/init.d/rcS /hceng
sync

umount /hceng
mount -t vfat /dev/mtdblock10 /hceng

cat /hceng/rcS
diff /hceng/rcS /etc/init.d/rcS

5. 补充

想到几个补充。

1.内核自带SPI设备驱动
在最开始的准备工作里,加入了User mode SPI device driver support
起初想的是一个SPI设备驱动用于测试,后来发现并不是简单的用于测试SPI,它还有更重要的作用,就如它的名字一样,“用户模式SPI设备驱动支持”,可以通过它,
在Linux应用层直接操作SPI设备。
参考自带的应用测试文件Documentation/spi/spidev_test.c,可以通过访问/dev/spidev1.0来控制SPI控制器,例如:

ioctl(fd, SPI_IOC_WR_MODE, mode); //修改SPI模式
ioctl(fd, SPI_IOC_RD_MODE, mode);

ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, speed); //修改SPI传输速度
ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, speed);

ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, bits); //修改SPI每次传输数据长度
ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, bits);

ioctl(fd, SPI_IOC_MESSAGE(1), &transfer); //控制SPI发送/接收数据
ioctl(fd, SPI_IOC_MESSAGE(1), &transfer);

通过该驱动的ioctl(),可以实现对SPI控制器的控制,也就可以通过它在用户层控制SPI设备,而不再需要单独为SPI设备写对应的驱动。
当然,它也有一些限制,比如这里的片选默认就是设备树指定的CS0,如果要使用其它片选,只能是在应用层操作/sys/class/gpio/x来模拟片选。它适用前面的DAC和OLED等字符设备,不适用于Flash等块设备。

2.w25q16dv驱动的一个Bug
使用前面编写的驱动,在将其作为字符设备操作是没有问题的,但作为块设备,先格式化,挂载的时候就会报参数错误。
通过排查,发现挂载的时候是要向Flash写入指定数据的,然而并没有写成功,因此挂载失败。
把前面每次传输的数据长度NUM由256改为8,可以减缓问题,可以挂载,也可以拷贝小文件,读取正常,但拷贝大文件后,重新挂载读取文件失败。
然后发现是DMA传输导致的错误,再往里检查发现是DMA和Cache的数据不一致导致的,这就比较深了,搞不定。
初步解决思路是在spi-omap2-mcspi.c中,先使用dma_alloc_coherent分配一个DMA Buffer,大小为4096,在SPI每次使用DMA方式的传输时:
①对于DMA写:
先把spi_transfer.tx_buf中的数据复制到事先分配的DMA Buffer,并把spi_transfer.tx_dma设置为该Buffer的DMA地址,最后再启动DMA传输;
②对于DMA读:
spi_transfer.rx_dma设置为事先分配的DMA Buffer的DMA地址,然后启动DMA传输;当传输完毕,把DMA Buffer中的数据复制到spi_transfer.rx_buf

你可能感兴趣的:(Linux驱动,嵌入式基础)