京东网站网站建设是什么网站建设费用明细表
目录
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 divideif(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 dividevalue = ((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 lowbreak;case SPI_MODE_2:case SPI_MODE_3:priv->mpsse_gpio_value |= ((u16)1 << 0); //Clock idle highbreak;
}
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 dataif (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 0x9Fuint8_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 Winbondsflash size 16MB, 24bits addressnor flash id is ef4018erasing nor flash ...erase time:391mswrite time:572mswrite speed:28 KB/s......read 553243 time:32msread speed:512 KB/s