ftdi_sio驱动学习笔记 8 - 增加MPSSE SPI

目录

1. 初始化

1.1 根据芯片设置SPI设备个数

1.2 注册SPI Master

1.3 创建SPI设备

2. 注销

3. ftdi_mpsse_transfer_one_message

3.1 切换到mpsse模式

3.2 分配命令缓存和FIFO

 3.3 初始化IO方向

3.4 初始化命令缓冲

3.5 设置SPI频率

3.6 不同模式初始电平

3.7 设置CS电平

3.8 SPI读写数据

3.8 返回处理

4. 验证

4.1 打开设备

4.2 读flash id

4.3 页编程

4.4 读

4.5 结果


增加SPI的方式类似I2C,每一路MPSSE的前面3个IO作为SPI口的SCK,MOSI和MISO,而其他的IO就可以作为SPI的CS脚控制不同的SPI设备。因此,对于FT232H/FT2232H来说,一路MPSSE可以支持13个SPI设备,而FT4232H一路MPSSE可以支持5个SPI设备。

1. 初始化

同样在ftdi_port_probe添加spi的初始化过程。

result = ftdi_mpsse_spi_init(port);
if(result < 0) {
	dev_err(&port->serial->interface->dev,
		"SPI initialisation failed: %d\n",
		result);
}

1.1 根据芯片设置SPI设备个数

在ftdi_mpsse_spi_init首先是根据芯片设置SPI设备个数

int spi_master_cs_num;

switch (priv->chip_type) {
	case FT232H:
	case FT232HP:
	case FT233HP:
	case FT2232H:
	case FT2232HP:
	case FT2233HP:
        spi_master_cs_num = 13;
        break;
	case FT4232H:
	case FT4232HP:
	case FT4233HP:
	case FT4232HA:
		spi_master_cs_num = 5;
		break;
	default:
		return -ENODEV;
}

1.2 注册SPI Master

在ftdi_private里面添加spi_master指针,

struct spi_master *spi_master;

分配master

priv->spi_master = spi_alloc_master(&port->dev, 0);
if(priv->spi_master == NULL) {
	return -ENOMEM;
}

注册master

priv->spi_master->dev.parent = &port->dev;
priv->spi_master->bus_num = -1;
priv->spi_master->num_chipselect = spi_master_cs_num;
priv->spi_master->transfer_one_message = ftdi_mpsse_transfer_one_message;
priv->spi_master->mode_bits = SPI_CPHA | SPI_CPOL;
spi_master_set_devdata(priv->spi_master, port);
ret = spi_register_master(priv->spi_master);
if (ret) {
    dev_err(&port->dev, "spi_register_master failed\n");
    goto spi_put_master;
}

bus_num为-1表示由系统分配master的总线编号,num_chipselect表示该master支持多少路CS,transfer_one_message是读写的函数,mode_bits设置模式。

1.3 创建SPI设备

SPI驱动构架中光有master是不够的,还需要driver和device。Linux提供了一个标准的device驱动spidev。通过API函数spi_new_device创建SPI设备。

for(i = 0; i < spi_master_cs_num; i++) {
    priv->spi_board[i].bus_num = priv->spi_master->bus_num;
    priv->spi_board[i].mode = SPI_MODE_0;
    priv->spi_board[i].chip_select = i;
    priv->spi_board[i].max_speed_hz = 10 * 1000 * 1000;
    //strscpy(priv->spi_board[i].modalias, "spidev", sizeof(priv->spi_board[i].modalias));
    strscpy(priv->spi_board[i].modalias, "dh2228fv", sizeof(priv->spi_board[i].modalias));
    priv->spi_dev[i] = spi_new_device(priv->spi_master, &priv->spi_board[i]);
    //dev_info(&port->dev, "spi_new_device %d success\n", i);
}

spi_board是每个SPI设备的配置,其中bus_num都是对应master的bus_num,表明是挂在该master上,mode默认都是模式0,chip_select表示该设备对应的CS脚,而max_speed_hz默认为10M。对于modalias,这个属性很重要,spidev通过这个属性来区别SPI设备,当这个属性配合时才会调用probe函数,一般情况下是需要修改spidev驱动里面的spidev_spi_ids,添加自己的name或者是通过设备树的方式匹配。

static const struct spi_device_id spidev_spi_ids[] = {
	{ .name = "dh2228fv" },
	{ .name = "ltc2488" },
	{ .name = "sx1301" },
	{ .name = "bk4" },
	{ .name = "dhcom-board" },
	{ .name = "m53cpld" },
	{ .name = "spi-petra" },
	{ .name = "spi-authenta" },
	{ .name = "em3581" },
	{ .name = "si3210" },
	{},
};

这里为了不修改spidev,直接将设备的name设置为"dh2228fv"。

此时可以看到/dev里面有创建对应的spi设备。

$ ls /dev/spidev*
/dev/spidev0.0  /dev/spidev0.10  /dev/spidev0.12  /dev/spidev0.3  /dev/spidev0.5  /dev/spidev0.7  /dev/spidev0.9
/dev/spidev0.1  /dev/spidev0.11  /dev/spidev0.2   /dev/spidev0.4  /dev/spidev0.6  /dev/spidev0.8

正好13个spi设备,前面的数字0表示spi master的bus num,后面的数字则对应spi device的bus num。

这里有个问题,如何判断SPI是FTDI设备生成的?

2. 注销

这一步比较简单,注销分配的资源,注销spi_master就可以了。

static void ftdi_mpsse_spi_remove(struct usb_serial_port *port) 
{ 
    struct ftdi_private *priv = usb_get_serial_port_data(port);
    spi_unregister_master(priv->spi_master);
    kfree(priv->spi_board);
    kfree(priv->spi_dev);
}

3. ftdi_mpsse_transfer_one_message

这个函数就是spi通信的具体实现。

static int ftdi_mpsse_transfer_one_message(struct spi_master *master,
					struct spi_message *message)

参数master表示这个设备对应的master,而message是spi读写的消息。

首先,CS脚控制的宏定义:

#define SPI_CS_GROUP(cs)    ((cs + 3) / 8)
#define SPI_CS_INDEX(cs)    (cs + 3)
#define SPI_SETCS_HIGH(cs)     { \
    priv->mpsse_gpio_value |= ((u16)1 << SPI_CS_INDEX(cs));\
}
#define SPI_SETCS_LOW(cs)      { \
    priv->mpsse_gpio_value &= (u16)(~((u16)1 << SPI_CS_INDEX(cs)));\
}

3.1 切换到mpsse模式

如果不是mpsse模式,需要先切换到mpsse模式

if(priv->bitmode != FTDI_SIO_BITMODE_MPSSE)
    ret = ftdi_set_mpsse_mode(port);
if(ret < 0){
    dev_err(&port->dev, "ftdi_set_mpsse_mode failed\n");
    goto spi_exit;
}

3.2 分配命令缓存和FIFO

首先计算命令缓存的大小和设置一次transfer的最大字节数。

const int transfer_max_once = 2 * 1024;
size_t spi_command_size = 4 /* set clock */ + 3 /* initial state */ + 3 * 2 /* CS low and high */ + 3 /* mpsse command */ 
            + transfer_max_once /* max 64K bytes once transfer */;

这里设置一次transfer最大字节数为2K的原因是usb write fifo的大小为4K,取其一半。 

分配命令缓存

spi_master_command = kmalloc(spi_command_size, GFP_KERNEL);
if(spi_master_command == NULL) {
    ret = -ENOMEM;
    goto spi_exit;
}

分配usb读FIFO

if(kfifo_alloc(&priv->read_fifo, transfer_max_once + 1, GFP_KERNEL)) {
    dev_info(&port->dev, "spi read fifo malloc fail\n");
    ret = -ENOMEM;
    goto spi_free_command;
}

 3.3 初始化IO方向

初始化4个IO的方向

priv->mpsse_gpio_output |= ((u16)0x03 << 0);   //SCK & MOSI set to output
priv->mpsse_gpio_output |= ((u16)1 << SPI_CS_INDEX(spi->chip_select));  //CS set to output
priv->mpsse_gpio_output &= ~((u16)1 << 2);   //MISO set to input

接下来就是处理transfer,一次message可以处理多个transfer,一次transfer是一次spi通信,对于FTDI设备需要注意一次通信数据不要超过65535个字节。

list_for_each_entry(transfer, &message->transfers, transfer_list) {  

}

3.4 初始化命令缓冲

将命令缓冲初始化为0xFF(当tx_buf为空时SPI的MOSI默认发送0xFF)

memset(spi_master_command, 0xff, spi_command_size);
spi_master_command_index = 0;

3.5 设置SPI频率

H系列芯片的频率分2种情况,6M以下需要分频(60M / 5 = 12M)。最低频率设置为1KHz。

if(spi->max_speed_hz <= 6 * 1000 * 1000) {
    spi_master_command[spi_master_command_index++] = 0x8b; //Enable clock divide
    if(spi->max_speed_hz == 0)
        spi->max_speed_hz = 1 * 1000;
    value = ((6 * 1000 * 1000) / spi->max_speed_hz) - 1;
} else {
    spi_master_command[spi_master_command_index++] = 0x8a;  //Disable clock divide
    value = ((30 * 1000 * 1000) / spi->max_speed_hz) - 1;
}
spi_master_command[spi_master_command_index++] = 0x86;
spi_master_command[spi_master_command_index++] = (value >> 0) & 0xff;
spi_master_command[spi_master_command_index++] = (value >> 8) & 0xff;

3.6 不同模式初始电平

根据SPI的初始模式设置SCK的初始电平

switch(spi->mode & SPI_MODE_X_MASK) {
    case SPI_MODE_0: 
    case SPI_MODE_1:   
        priv->mpsse_gpio_value &= ~((u16)1 << 0); //Clock idle low
        break;
    case SPI_MODE_2:
    case SPI_MODE_3:
        priv->mpsse_gpio_value |= ((u16)1 << 0); //Clock idle high
        break;
}
spi_master_command[spi_master_command_index++] = gpioWriteCommand[0];\
spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_value >> 0 * 8) & 0xff;
spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_output >> 0 * 8) & 0xff;

频率和初始电平只能设置一次,在第一个transfer中设置即可。 

3.7 设置CS电平

第一笔transfer肯定是要设置CS使能的,所以变量cs_change开始是1

if (cs_change) {
    if(spi->mode & SPI_CS_HIGH) {
        SPI_SETCS_HIGH(spi->chip_select);
    } else {
        SPI_SETCS_LOW(spi->chip_select);
    }
    spi_master_command[spi_master_command_index++] = gpioWriteCommand[SPI_CS_GROUP(spi->chip_select)];
    spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_value >>     (SPI_CS_GROUP(spi->chip_select) * 8)) & 0xff;
    spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_output >> (SPI_CS_GROUP(spi->chip_select) * 8)) & 0xff;
}
cs_change = transfer->cs_change;

根据spi的模式设置判断CS是低有效还是高有效设置CS的电平。

设置后根据transfer的设定改变cs_change的值,这样的设计可以实现多笔transfer时cs的控制随用户设置,比如一个cs使能过程多笔数据的传输。

3.8 SPI读写数据

H系列设备每次读写的数据长度不能超过64KB,但是因为写FIFO的问题,每2KB读写一笔数据。

int tr_size = transfer->len;
int tr_offset = 0;
while(tr_size > 0) {
    int count = min(tr_size, transfer_max_once);
    ......
}

根据模式和MSB/LSB设置不同的命令,后面2个字节的命令表示数据长度,注意数字0表示数据长度为1。

if(spi->mode & SPI_LSB_FIRST) {
    lsb = 0x08;
}
switch(spi->mode & SPI_MODE_X_MASK) {
    case SPI_MODE_0:
        spi_master_command[spi_master_command_index++] = 0x31 + lsb;
        break;
    case SPI_MODE_1:
        spi_master_command[spi_master_command_index++] = 0x34 + lsb;
        break;
    case SPI_MODE_2:
        spi_master_command[spi_master_command_index++] = 0x34 + lsb;
        break;
    case SPI_MODE_3:
        spi_master_command[spi_master_command_index++] = 0x31 + lsb;
        break;
}
spi_master_command[spi_master_command_index++] = (transfer->len - 1) & 0xFF;
spi_master_command[spi_master_command_index++] = ((transfer->len - 1) >> 8) & 0xFF;

然后把要写的数据拷贝的命令缓冲中,wr_buf等于transfer->tx_buf

if(wr_buf) 
    memcpy(spi_master_command + spi_master_command_index, wr_buf + tr_offset, count);
spi_master_command_index += count;

如果是最后一笔数据,判断是否要处理CS控制

tr_size -= count;
if(tr_size == 0) { //last data
    if (cs_change) {
        if(spi->mode & SPI_CS_HIGH) {
            SPI_SETCS_LOW(spi->chip_select);
        } else {
            SPI_SETCS_HIGH(spi->chip_select);
        }
        spi_master_command[spi_master_command_index++] = gpioWriteCommand[SPI_CS_GROUP(spi->chip_select)];
        spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_value >> (SPI_CS_GROUP(spi->chip_select) * 8)) & 0xff;
        spi_master_command[spi_master_command_index++] = (priv->mpsse_gpio_output >> (SPI_CS_GROUP(spi->chip_select) * 8)) & 0xff;
    }
}

写之前判断一下数据有没有溢出

if(spi_master_command_index > spi_command_size) {
    dev_err(&port->dev, "spi command buffer is not enough\n");
    ret = -ENOMEM;
    goto spi_free_read_fifo;
}

然后配置好读入数据长度,在写之前先确认一下FIFO中有没有数据,有的话就读空FIFO。

priv->read_len = count;
read_len = kfifo_len(&priv->read_fifo) ;
if(read_len > 0) {
    u8 *dummy;
    dummy = kmalloc(read_len, GFP_KERNEL);
    ret = kfifo_out(&priv->read_fifo, dummy, read_len);
    kfree(dummy);
}
read_len = priv->read_len;

写数据且等待读入数据完成

ret = ftdi_usb_write(port, spi_master_command, spi_master_command_index, false);
if(ret < 0) {
    dev_err(&port->dev, "ftdi_usb_write failed\n");
    ret = -EIO;
    goto spi_free_read_fifo;
}
spi_master_command_index = 0;

while (retry_count--) { 
    ret = wait_event_interruptible_timeout(priv->read_wait, !priv->read_len, HZ  * (priv-    >read_len / port->bulk_out_size + 1) / 10); 
    if (ret == -ERESTARTSYS) {
        ret = -EINTR;// 信号中断睡眠
        if (retry_count > 0) {  
            // 尝试重新等待  
            continue;  
        } else {
            dev_err(&port->dev, "Read signal break\n");
            goto spi_free_read_fifo;
        } 
    } else if (!ret) {  // 超时
        dev_err(&port->dev, "Read pending timeout\n");
        priv->read_len = 0; // 防止死循环
        ret = -ETIMEDOUT;
        goto spi_free_read_fifo;
    }
}

如果读入数据没有错误,将FIFO里面的数据拷贝到transfer->rx_buf。如果是只写的情况,也把FIFO中的数据读空一下。

if(rd_buf) {
    ret = kfifo_out(&priv->read_fifo, rd_buf + tr_offset, read_len);
    if (ret < 0) {
         dev_err(&port->dev, "kfifo_out failed\n");
         ret = -EIO;
         goto spi_free_read_fifo;
    }
} else {
    u8 *dummy;
    read_len = kfifo_len(&priv->read_fifo);

    dummy = kmalloc(read_len, GFP_KERNEL);
    ret = kfifo_out(&priv->read_fifo, dummy, read_len);
    kfree(dummy);
}
tr_offset += count;

3.8 返回处理

返回需要更新真正传输数据长度

message->actual_length += count; 

还需要更新status

message->status = ret;

完成当前消息的传输结束操作

spi_finalize_current_message(master);

4. 验证

将一颗nor flash接在spidev0.0上面。

4.1 打开设备

if ((spi_fd = open("/dev/spidev0.0", O_RDWR)) < 0) {
    printf("Open spi fail\n");
    return;
}

4.2 读flash id

读id的命令是0x9F,读入3个字节的数据就是flash的id

#define CMD_READ_ID             0x9F

uint8_t cmd[4] = {CMD_READ_ID, 0xff, 0xff, 0xff};

初始化一个transfer

struct spi_ioc_transfer spi;
 
memset(&spi, 0, sizeof (spi));
 
spi.tx_buf = (unsigned long)cmd;
spi.rx_buf = (unsigned long)idbuf;
spi.len = 4;
spi.delay_usecs = 0;
spi.speed_hz = 10 * 1000 * 1000;
spi.bits_per_word = 8;
spi.cs_change = 1;
 
ioctl(fd, SPI_IOC_MESSAGE(1), &spi);

写入的4个字节在cmd里面,同时读入的4个字节在idbuf中,其中第一个字节不需要,没意义。

id = (idbuf[1] << 16) | (idbuf[2] << 8) | idbuf[3];

执行这个后可以读到id号

nor flash id is ef4018

4.3 页编程

flash的页编程分成2部分,写地址(写一个字节命令字0x02和3字节的地址)和写数据,可以组合成一个transfer写(追求速度应该是采用这种方式),这里采用2个transfer的方式写。

void sflashPageProgram(int fd, uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[5] = {CMD_PAGE_PROGRAM, 0, 0, 0, 0};
    uint8_t offset = 1;

    struct spi_ioc_transfer spi[2];
    memset(&spi, 0, sizeof (spi));
 
    spi[0].tx_buf = (unsigned long)cmd;
    spi[0].delay_usecs = 0;
    spi[0].speed_hz = SFLASH_SPI_CLK_HZ;
    spi[0].bits_per_word = 8;
    spi[0].cs_change = 0;

    //if(sflash[port].addrSize == SFLASH_ADDR_32BIT)
    //    cmd[offset++] = (uint8_t)(addr >> 24);
    cmd[offset++] = (uint8_t)(addr >> 16);
    cmd[offset++] = (uint8_t)(addr >> 8);
    cmd[offset++] = (uint8_t)(addr >> 0);
    spi[0].len = offset;
    
    spi[1].tx_buf = (unsigned long)buf;
    spi[1].delay_usecs = 0;
    spi[1].speed_hz = SFLASH_SPI_CLK_HZ;
    spi[1].bits_per_word = 8;
    spi[1].cs_change = 1;
    spi[1].len = len;

    sflashWriteEnable(fd);

    ioctl(fd, SPI_IOC_MESSAGE(2), &spi);

    usleep(2000);
    sflashWaitFree(fd, 2);
    sflashWriteDisable(fd);
    //printf("page program %x finished\n", addr);
}

因为第一笔数据写完不能把CS设置为Disable,所以第一笔transfer的cs_change设置为0.

4.4 读

nor flash读不需要有页这个概念,但是这里还是为了避免一次读太多数据,添加一个页读的函数。

void sflashPageRead(int fd, uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint8_t cmd[6] = {CMD_FASTREAD, 0, 0, 0, 0, 0};
    uint8_t offset = 1;

    struct spi_ioc_transfer spi[2];
    memset(&spi, 0, sizeof (spi));
 
    spi[0].tx_buf = (unsigned long)cmd;
    spi[0].delay_usecs = 0;
    spi[0].speed_hz = SFLASH_SPI_CLK_HZ;
    spi[0].bits_per_word = 8;
    spi[0].cs_change = 0;

    //if(sflash[port].addrSize == SFLASH_ADDR_32BIT)
    //    cmd[offset++] = (uint8_t)(addr >> 24);
    cmd[offset++] = (uint8_t)(addr >> 16);
    cmd[offset++] = (uint8_t)(addr >> 8);
    cmd[offset++] = (uint8_t)(addr >> 0);

    spi[0].len = offset + 1;

    spi[1].rx_buf = (unsigned long)buf;
    spi[1].delay_usecs = 0;
    spi[1].speed_hz = SFLASH_SPI_CLK_HZ;
    spi[1].bits_per_word = 8;
    spi[1].cs_change = 1;
    spi[1].len = len;

    ioctl(fd, SPI_IOC_MESSAGE(2), &spi);
}

4.5 结果

读写16K数据,记录读写速度(设置的时钟频率为10M),然后循环读取flash数据与写入的随机数比较,如果出错就退出,log信息如下:

sflash Winbond

sflash size 16MB, 24bits address

nor flash id is ef4018

erasing nor flash ...

erase time:391ms

write time:572ms

write speed:28 KB/s

......

read 553243 time:32ms

read speed:512 KB/s

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值