欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AM335X——SPI设备驱动

程序员文章站 2024-01-31 11:36:34
...

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

AM335X——SPI设备驱动

1.2 设备树

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

aaa@qq.com {
    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 */

};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

};

&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  | aaa@qq.com▒..................▒.
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  | ................................
  • 1
  • 2
  • 3
  • 4
  • 5

此时可以通过逻辑分析仪或者短接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 -jCPUSmakezImagejCPUSmakezImagejCPUSmakezImagejCPUSmakezImage−jCPUSmakezImage−j {CPUS}make zImage -jCPUSmakezImagej{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} optargsroot=/dev/nfsrootfstype=nfsrootnfsroot={optargs} root=/dev/nfs rootfstype=nfsroot nfsroot={serverip}:rootpathip={rootpath} ip={ipaddr}:serverip:{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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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>;

aaa@qq.com {
    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       */

};

aaa@qq.com {
spi-max-frequency = <25000000>;
reg = <1>;
compatible = “ti,tlc5615”;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

};

&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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

error:
unregister_chrdev_region(devid, 1);

return -EINVAL;
  • 1

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

}

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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// ./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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

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 <linux/gcd.h>

#include <linux/spi/spi.h>

+#include <linux/gpio.h>
#include <linux/platform_data/spi-omap2-mcspi.h>

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

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

}
{% 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>;

aaa@qq.com {
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 */

};

aaa@qq.com {
compatible = “ti,tlc5615”;
spi-max-frequency = <25000000>;
reg = <1>;
};

aaa@qq.com {
compatible = “solomon,ssd1306fb-spi”; //OLED
spi-max-frequency = <25000000>;
reg = <2>;
dc-gpio = <&gpio0 12 0>;
pinctrl-0 = <&oled_dc_pin>;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

};

&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 */
    >;
};
  • 1
  • 2
  • 3
  • 4
  • 5

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

3.2 驱动程序

{% codeblock lang:c [fb_ssd1306_drv.c] %}
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

#include <linux/spi/spi.h>

#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 
  • 1
  • 2

}

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/

  • 1
  • 2
  • 3
  • 4
  • 5

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

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

err2:
kfree(ker_buf);

err1:
gpio_free(spi_oled_dc_pin);

return -1;
  • 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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

}

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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

/* 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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

}
{% 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大小;
    它们之间大小关系如下:
AM335X——SPI设备驱动
另外,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寄存器里面,如下图路线②;
AM335X——SPI设备驱动
  • 3.函数调用关系分析
    在应用层写操作w25q16dv时,会调用到w25q16dv_drv.c
w25q16dv_write();
    spi_flash_program();
        spi_sync();
  • 1
  • 2
  • 3

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

__spi_sync();
    __spi_pump_messages();
        master->transfer_one_message(master, master->cur_msg);	
  • 1
  • 2
  • 3

再调用到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传输		
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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>;

aaa@qq.com {
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 */

};

aaa@qq.com {
compatible = “ti,tlc5615”;
spi-max-frequency = <25000000>;
reg = <1>;
};

aaa@qq.com {
compatible = “solomon,ssd1306fb-spi”; //OLED
spi-max-frequency = <25000000>;
reg = <2>;
dc-gpio = <&gpio0 12 0>;
pinctrl-0 = <&oled_dc_pin>;
};

aaa@qq.com {
spi-max-frequency = <20000000>;
reg = <3>;
compatible = “winbond,w25q16dv”;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

};

&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 */
    >;
};
  • 1
  • 2
  • 3
  • 4
  • 5

};
{% endcodeblock %}

4.2 程序驱动

{% codeblock lang:c [w25q16dv_drv.c] %}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>

#include <linux/mtd/cfi.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>

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];

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

}

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;

  • 1
  • 2
  • 3

}

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;

  • 1
  • 2
  • 3

}

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();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

}

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);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

}

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);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

}

/* 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();

  • 1
  • 2
  • 3
  • 4
  • 5

}

/* 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();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

}

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);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

}

#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&lt;page_num; i++)
    spi_flash_program(data_addr+NUM*i+left_len, (unsigned char *)(buf+NUM*i+left_len), NUM);    
spi_flash_program(data_addr+NUM*i+left_len, (unsigned char *)(buf+NUM*i+left_len), right_len);

}

*retlen = data_len;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

#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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

#endif

return 0;
  • 1

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

}

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

mtd_device_unregister(&flash_w25q16dv_dev);

return 0;

  • 1
  • 2
  • 3

}

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 <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <mtd/mtd-user.h>
    #include <errno.h>
    #include <getopt.h>

#include <assert.h>

// 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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

};

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);
  • 1

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

}

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 &gt; 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, &amp;erase) != 0)
        {
            perror("MTD unlock failure");
            close(fd);
            return -1;
        }
    }
    if(ioctl(fd, MEMERASE, &amp;erase) != 0)
    {
        perror("MTD erase failure");
        close(fd);
        return -1;
    }
    erase.start += meminfo.erasesize;
}
printf("  done\n");

}
return 0;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

}

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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

}

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 &lt; 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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

}

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) &amp;&amp; (i &lt; maxlen))
{
    // place the hex numbers from the line read in to the temp buffer
    sscanf(temp, "%08lx %08lx %08lx %08lx %08lx %08lx %08lx %08lx", \
           &amp; (b[0]), &amp;(b[1]), &amp;(b[2]), &amp;(b[3]), \
           &amp; (b[4]), &amp;(b[5]), &amp;(b[6]), &amp;(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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

}

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, &regcount) == 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;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

}
{% 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

flash_eraseall /dev/mtd10
  • 1
  • 当作文件系统测试:
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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

通过该驱动的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

                                </div>
相关标签: AM335x