spi驱动初试,在linux下实现的spiflash驱动

rk3288上还留有一个可以外接设备的spi0,所以我使用的是spi0,设备树文件如下,设备树中我没有指定CPOL和CPHA,在probe里手动赋值的

static int flash_spi_probe(struct spi_device *spi)
{
        struct spidev_data        *spidev;
        struct device_node  *np = spi->dev.of_node;   /*Get device tree node*/
        int                                         status;
        unsigned long                 minor;
        int                  ret;

        printk("%s \n", __FUNCTION__);

        /* Allocate driver data */
        spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
        if (!spidev)
                return -ENOMEM;

        /* Initialize the driver data */
        spi->mode = 1;
        spi->max_speed_hz = 1000 * 1000 *10;
        spi->bits_per_word = 8;
        spidev->spi = spi;

        /*inin lock*/
        spin_lock_init(&spidev->spi_lock);
        mutex_init(&spidev->buf_lock);

        /*Reset register condition*/
        At45db_nrst = of_get_named_gpio(np, "At45db_nrst", 0);

        ret = gpio_request(At45db_nrst, "At45db_nrst");
        if (ret)
                return -ENODEV;
        gpio_direction_output(At45db_nrst, GPIO_LOW);
        gpio_direction_output(At45db_nrst, GPIO_HIGH);

        /*Turn off write protect*/
        At45db_nwp = of_get_named_gpio(np, "At45db_nwp", 0);
        ret = gpio_request(At45db_nwp, "At45db_nwp");
        if (ret)
                return -ENODEV;

        //At45db_flash_init(spidev);

        INIT_LIST_HEAD(&spidev->device_entry);

        /* If we can allocate a minor number, hook up this device.
         * Reusing minors is fine so long as udev or mdev is working.
         */
        mutex_lock(&device_list_lock);
        minor = find_first_zero_bit(minors, N_SPI_MINORS);
        if (minor < N_SPI_MINORS) {
                struct device *dev;

                spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
                dev = device_create(spiflash_cls, &spi->dev, spidev->devt,
                                    spidev, "spidev%d.%d",
                                    spi->master->bus_num, spi->chip_select);
                status = PTR_RET(dev);
        } else {
                dev_dbg(&spi->dev, "no minor number available!\n");
                status = -ENODEV;
        }
        if (status == 0) {
                set_bit(minor, minors);
                list_add(&spidev->device_entry, &device_list);
        }
        mutex_unlock(&device_list_lock);

        if (status == 0)
                spi_set_drvdata(spi, spidev);
        else
                kfree(spidev);

        return status;
}
和设备树匹配的代码,基本都是这个套路,匹配compatible就行了

flash_spi.rar (3.24 KB)

AT45DB041D.rar (708 KB)

本帖最后由 z3j6w9 于 2017-7-12 14:38 编辑

在probe中修改了max_frequence,是因为执行写操作报warning
spi_master spi0: DW SPI: Status keeps busy for 1000us after a read/write!
其实就是频率有点快,调慢点就行了

spi flash读数据前都需要写命令,刚开始先发命令再调用read获取数据,怎么都调不对波形,后来参看了下韦东山写的spi flash的代码,写成如下的形式就ok了;韦东山的spi视频是要花钱的,不过就19块,有兴趣的可以看看,我最近比较懒,所以就没去买来看了。

static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, FLASH_C *flash_c)
{
        struct spi_transfer        t[] = {
                {
                        .tx_buf                = flash_c->cmd,
                        .len                = flash_c->cmd_len,
                },
                {
                        .rx_buf     = flash_c->read_buf,
                        .len        = flash_c->read_buf_len,
                }
        };
        struct spi_message        m;

        spi_message_init(&m);
        spi_message_add_tail(&t[0], &m);
        spi_message_add_tail(&t[1], &m);
        return spidev_sync(spidev, &m);
}

本帖最后由 z3j6w9 于 2017-7-12 14:48 编辑

在对spi进行操作之前需要获取下当前芯片的状态,在初始化过程中操作是没有回应的。还有就是,我一开始在初始化后就尝试获取状态,因为调用的是read函数,结果就在如下代码中挂掉了,程序状态直接变成了D+,而且不可中断,重启无数次。

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
        DECLARE_COMPLETION_ONSTACK(done);
        int status;

#if 1
        message->complete = spidev_complete;
        message->context = &done;
#endif
        spin_lock_irq(&spidev->spi_lock);
        if (spidev->spi == NULL)
                status = -ESHUTDOWN;
        else
                status = spi_async(spidev->spi, message);
        spin_unlock_irq(&spidev->spi_lock);

        if (status == 0) {
                wait_for_completion(&done);
                status = message->status;
                if (status == 0)
                        status = message->actual_length;
        }
        return status;
}

后续就改成了在每次读写执行获取芯片状态,不在锁里面做,防止死掉挂掉的时候会提示hung task,分享个别人写的定位的博客,其实写代码的过程中遇到问题是好事,真的能学会很多
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&id=4406510&uid=14528823

本帖最后由 z3j6w9 于 2017-7-12 15:05 编辑

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

typedef struct TEST_Flash_Ctrl
{ 	unsigned char *cmd;
	unsigned char *read_buf;
	unsigned char *w_data;
	unsigned char  cmd_len;
	unsigned char  read_buf_len;
	unsigned char  w_len;
	unsigned char  resv;
}TEST_C;

static int fd;
static int write_to_flash(TEST_C *buf)
{
	return write(fd, buf, sizeof(TEST_C));
}

static int read_from_flash(TEST_C *buf)
{
	return read(fd, buf, buf->read_buf_len);
}

static int init_flash()
{
	/*erase before write*/
	int ret ;
	TEST_C buf;
	unsigned char erase_cmd[] = {0xc7, 0x94, 0x80, 0x9a};

	buf.cmd = erase_cmd;
	buf.cmd_len = sizeof(erase_cmd);

	ret = write_to_flash(&buf);
	if (0 == ret)
	{
		printf("%s: write err.\n ", __FUNCTION__);
		return ret;
	}

	return 0;
}

static int write_buffer(unsigned short addr, unsigned char* data, unsigned char len)
{
	unsigned char w_cmd[4];
	unsigned char addr_u = (unsigned char)(addr >> 8);
	unsigned char addr_v = (unsigned char)addr;
	int ret;
	TEST_C buf;

	w_cmd[0] = 0x84;
	w_cmd[1] = 0xff;
	w_cmd[2] = addr_u;
	w_cmd[3] = addr_v;

	buf.cmd = w_cmd;
	buf.cmd_len = sizeof(w_cmd);
	buf.w_data = data;
	buf.w_len  = len;

	ret = write_to_flash(&buf);
	if (0 == ret)
	{
		printf("%s: write err.\n ", __FUNCTION__);
		return ret;
	}

	return 0;
}

static int read_buffer(unsigned short add, unsigned char *read_buf, unsigned char len)
{

	unsigned char r_cmd[5];
	unsigned char addr_u;
	unsigned char addr_v;
	TEST_C test_c;
	TEST_C buf;

	addr_u = (unsigned char)(add >> 8);
	addr_v = (unsigned char)add;

	r_cmd[0] = 0xd4;
	r_cmd[1] = 0xff;
	r_cmd[2] = addr_u;
	r_cmd[3] = addr_v;
	r_cmd[4] = 0xff;

	buf.cmd = r_cmd;
	buf.cmd_len = sizeof(r_cmd);
	buf.read_buf = read_buf;
	buf.read_buf_len = len;

	read_from_flash(&buf);
}

static unsigned char read_status()
{
	TEST_C buf;
	unsigned char cmd[1];
	unsigned char status = 0;
	printf("Get status \n");

	cmd[0] = 0xd7;

	buf.cmd = cmd;
	buf.cmd_len = 1;
	buf.read_buf = &status;
	buf.read_buf_len = 1;

	read_from_flash(&buf);

	return status;
}

int main(void)
{
	int ret = 0;
	unsigned char buf_room[10];
	unsigned char status;
	unsigned short addr = 0x0;
	unsigned char data[] = {0x00, 0xff, 0x0, 0xff, 0x0, 0xff};

	fd = open("/dev/spidev0.0", O_RDWR);
	if (fd < 0)
	{
		printf("%s open device failed. \n", __FUNCTION__);
		return -1;
	}
	init_flash();
#if 1
	write_buffer(addr, data, sizeof(data));
	read_buffer(addr, buf_room, 8);

	printf("Data is %x \n", buf_room[0]);
	printf("Data is %x \n", buf_room[1]);
	printf("Data is %x \n", buf_room[2]);
#endif
	ret = close(fd);

	return 0;
}

上面的是接收的数据,下面这张是发送的波形

代码中只实现了对buffer的写/读,有兴趣研究嵌入式mcu开发的可以在论坛中私信我,大家私下一起交流技术

请问自己新增一个档案当作driver,都需要修改dts档案吗??

aaaaa-ray 发表于 2017-7-21 11:24
请问自己新增一个档案当作driver,都需要修改dts档案吗??

dts中写的是硬件相关的信息,如果你需要用到的硬件信息设备树中已经有了,那就不需要修改设备树了。设备树的出现其实就是替代了以前驱动中的board_info注册,将通用的硬件信息给整合了起来,既方便阅读,也方便开发。

z3j6w9 发表于 2017-7-21 13:05
dts中写的是硬件相关的信息,如果你需要用到的硬件信息设备树中已经有了,那就不需要修改设备树了。设备 …

太感谢解答

严重的感谢解答

感谢解答

楼主,我最近入手了一块RK3328开发板,打算在预留的40pin插针上接一个spi设备,可是接线的时候发现压根就没有IRQ和RESET脚预留出来,管脚服用好像也不是太行,卡在这里了,请楼主点拨。附上原理图:

luokaisong130 发表于 2018-3-9 15:03
楼主,我最近入手了一块RK3328开发板,打算在预留的40pin插针上接一个spi设备,可是接线的时候发现压根就没 …

irq和reset都是可以接普通GPIO的,先来说说irq,所谓的中断,其实就是监测一个电位的变化,将GPIO设置为中断模式完全可以做到,reset就更简单了,通过程序控制电平即可,一般情况下很多芯片的RESET上面都有取反符号为低电平有效,所以程序运行时设置为高电平即可。

z3j6w9 发表于 2018-3-18 10:06
irq和reset都是可以接普通GPIO的,先来说说irq,所谓的中断,其实就是监测一个电位的变化,将GPIO设置为 …

谢谢大神,我后面也是这么做的,第一次玩嵌入式是小白,哈哈

楼主,你好,我参考你的代码,在aio-3288j主板上进行测试,代码编译成功,/dev目录下也产生了设备节点;我使用的是aio-3288j主板上真实的spi2,用逻辑分析仪抓取波形,没有输出;
我初步推断,可能是At45db_nwp和At45db_nrst的gpio设置问题,这个需要怎么设置?给些指点,谢谢!
我的aio-3288j主板dts中spi2设置见图1,spi2的管脚见图2


mark