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