CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/01/AM335X——SPI设备驱动/#more
最近在AM335X上写了几个SPI设备驱动,记录一下心得。
与前面写过的I2C驱动一样,SPI驱动也涉及SPI控制器(适配器)驱动和SPI设备驱动。
SPI控制器驱动这里不就写了,直接使用SDK自带的,只写SPI设备的驱动。
在写SPI设备驱动之前,需要先验证下SDK提供的SPI驱动是否能用,相关的设置是否正确,因此使用内核自带的一个SPI设备驱动进行测试。
首先配置内核,执行make menuconfig
,勾选上McSPI driver for OMAP
和User mode SPI device driver support
。
然后还要向设备树添加如下内容:
{% 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.内核自带的测试驱动程序的compatible
为rohm,dh2228fv
;
使用内核提供的测试程序,编译,测试。
测试文件路径: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控制器驱动是否正常。
为了方便后续的编译,写了一个脚本进行操作,这个脚本也相当于操作流程,以供参考:
{% 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 CPUSmakezImage−j{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
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);
;
{% 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 %}
{% 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 %}
{% 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位数据。
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_select
和spi->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
#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 (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);
}
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 %}
{% 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功能;
{% 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设备驱动程序没什么特别的。
{% codeblock lang:c [oled_app.c] %}
#include
#include
#include
#include
#include
#include
#include
#include
/* oled_test init
#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 %}
w25q16dv是一个2M大小的Flash。
在写驱动之前,需要了解下这几个知识点:
w25q16dv
的Byte
、Page
、Sector
、Block
之间的关系Byte
(字节)由8个bits
(位)组成,是数据的最小存储单位,1024个Byte
就是我们常见的1KB;Page
(页)由256个Byte
组成,w25q16dv
每次SPI写操作只能是1~256个字节,因此遇到大数据传输时,需要分页写;Sector
(扇区)由16个Byte
组成,也是4K大小,w25q16dv
每次SPI擦除操作就是以Sector
为最小单位;Block
(块)由16个Sector
组成,为64K大小;DMA_MIN_BYTES
,则不会启用DMA,CPU会先读取RAM中的数据,再写到SPI控制器的TX_BUF
寄存器里面,如下图路线①;DMA_MIN_BYTES
,则会启用DMA,CPU设置好DMA后,DMA自动从RAM搬运数据到SPI控制器的TX_BUF
寄存器里面,如下图路线②;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传输
{% 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 %}
{% 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 %}
这是遇到过数不多的块设备驱动,块设备驱动的写法都要忘了,以后块设备驱动可以参考这个。
在加载w25q16dv.ko
驱动后,将会生成两个设备节点:/dev/mtdx
和/dev/mtdblockx
。
/dev/mtdx
是字符设备的节点,可以像字符设备那样读写;
/dev/mtdblockx
是块设备的节点,可以进行块设备类似的操作;
两个节点都是操作的同一设备,而且最后调用的读写函数也是驱动中的同一个;
两者差异体现在进行字符设备读写时,可以直接写到某个地址,而对块设备而言,需要先读块的内容,再擦除一个块,再写一个块。
测试方法有两种,
一种是自己写应用程序,通过open()
、read()
、write()
等进行操作;
另一种是把整个Flash格式化成各种文件系统格式,然后挂载,拷贝文件,卸载,再次挂载检查之前拷贝的文件是否正常。
#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
想到几个补充。
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_coheren
t分配一个DMA Buffer
,大小为4096,在SPI每次使用DMA方式的传输时:
①对于DMA写:
先把spi_transfer.tx_buf
中的数据复制到事先分配的DMA Buffe
r,并把spi_transfer.tx_dma
设置为该Buffer
的DMA地址,最后再启动DMA传输;
②对于DMA读:
把spi_transfer.rx_dma
设置为事先分配的DMA Buffer
的DMA地址,然后启动DMA传输;当传输完毕,把DMA Buffer中
的数据复制到spi_transfer.rx_buf
;