目录
1. ftdi_private
2. 设置MPSSE模式
3. USB写函数
4. USB读操作
5. mpsse写gpio
6. mpsse读gpio
7. ftdi_gpio_init
8. ftdi_gpio_request
9. ftdi_gpio_free
10. ftdi_gpio_direction_get
11. ftdi_gpio_direction_input
12. ftdi_gpio_direction_output
13. ftdi_gpio_init_valid_mask
14. ftdi_gpio_get
15. ftdi_gpio_set
15. ftdi_gpio_get_multiple
16. ftdi_gpio_set_multiple
17. 验证
之前GPIO是基于CBUS的模式,对于H系列芯片还支持MPSSE模式。注意CBUS模式和MPSSE模式不能同时使用。基本是根据CBUS模式修改。
1. ftdi_private
一路MPSSE最多支持16个GPIO,所以在ftdi_private里面增加3个u16的变量来表示这些gpio的属性。
#ifdef CONFIG_GPIOLIBstruct gpio_chip gc;struct mutex gpio_lock; /* protects GPIO state */bool gpio_registered; /* is the gpiochip in kernel registered */bool gpio_used; /* true if the user requested a gpio */u8 gpio_altfunc; /* which pins are in gpio mode */u8 gpio_output; /* pin directions cache */u8 gpio_value; /* pin value for outputs */u16 mpsse_gpio_altfunc;u16 mpsse_gpio_output;u16 mpsse_gpio_value;
#endif
mpsse_gpio_altfunc这个变量每个位用于在系统上显示该GPIO是否被占用。 mpsse_gpio_output表示GPIO的方向,而mpsse_gpio_value表示GPIO的电平。
2. 设置MPSSE模式
首先在ftdi_sio.h里面添加MPSSE模式的参数值。
#define FTDI_SIO_BITMODE_MPSSE 0x02
然后是设置MPSSE模式的函数
static int ftdi_set_mpsse_mode(struct usb_serial_port *port)
{return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_MPSSE);
}
在ftdi_private添加一个变量bitmode用来表示芯片当前处于什么模式。
u8 bitmode;
然后在ftdi_set_bitmode里面改变这个变量
priv->bitmode = mode;
因为mpsse的读写是需要通过串口的读写流程实现的,所以这里会根据进出mpsse模式打开或关闭串口,另外还需要设置latency以方便读写速度最快(H系列最小可以设置为0,其他则为4,这个值可能需要根据平台调试,如果设置为2,长时间测试可能会读数据错误)。
if(mode == FTDI_SIO_BITMODE_MPSSE || mode == FTDI_SIO_BITMODE_ASYNC_BITBANG || mode == FTDI_SIO_BITMODE_SYNC_BITBANG) {struct tty_struct *tty = tty_port_tty_get(&port->port); unsigned int latency = priv->latency;usb_serial_generic_open(tty, port);switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->latency = 0; //For H serial, it can be set to 0.break;default:priv->latency = 4;}write_latency_timer(port);priv->latency = latency;} else {usb_serial_generic_close(port);write_latency_timer(port);}
3. USB写函数
需要添加写数据的api函数。这个函数的功能是把数据通过USB写给FTDI设备,串口默认那个写只是写缓冲,这里是直接通过usb发送。
static int ftdi_usb_write(struct usb_serial_port *port,const unsigned char *data, int count, bool wait)
{
}
参数data是要写出去的数据,count是数据的长度,wait是表示是否等待写数据结束。
直接调用串口的写函数
result = usb_serial_generic_write(tty, port, data, count);
if(result < 0){dev_err(&port->dev, "Write failed\n");return result;
}
这个API函数会先把data写入到端口的FIFO中,然后找到空闲的写urb(FT232H有2个写urb),配置完urb后提交urb即可,并不会等待数据发送结束,而对于mpsse应用,写完后需要等待数据写完,这里是设置了以100ms为单位的超时错误。
if(wait) {result = wait_event_interruptible_timeout(priv->write_wait, !port->tx_bytes, HZ * (count / priv->max_packet_size + 1) / 10);if (result == -ERESTARTSYS) {// 信号中断睡眠result = -EINTR;} else if (!result) {// 超时dev_err(&port->dev, "Write pending timeout\n");result = -ETIMEDOUT;} else {result = count;}
}
write_wait是一个等待队列头结构体变量,需要在结构体ftdi_private中添加(需要添加头文件<linux/wait.h>)
wait_queue_head_t write_wait;
需要增加一个写数据结束的回调函数以唤醒等待队列。
static void ftdi_write_bulk_callback(struct urb *urb)
{struct usb_serial_port *port = urb->context;struct ftdi_private *priv = usb_get_serial_port_data(port);usb_serial_generic_write_bulk_callback(urb);if(priv->bitmode == FTDI_SIO_BITMODE_MPSSE || priv->bitmode == FTDI_SIO_BITMODE_SYNC_BITBANG) {if(urb->status == 0) {if(port->tx_bytes == 0) {wake_up_interruptible(&priv->write_wait); // 唤醒等待队列 }}}
}
这里的回调函数需要在ftdi_device里面添加
.write_bulk_callback = ftdi_write_bulk_callback,
由于这一步会覆盖标准的串口写回调函数,所以在ftdi_write_bulk_callback里面会先调用标准的回调函数usb_serial_generic_write_bulk_callback。
4. USB读操作
读是不用设置的,但是当前读到数据后的回调函数是ftdi_process_read_urb。一旦打开设备,urb读请求就会设置好,然后读入数据后会继续设置urb读请求。而FTDI芯片会一直发送数据,如果没有实际发送的数据,也会发送2个字节的modem状态,而如果有实际数据,前面2个字节也是这个状态。
首先在ftdi_private里面添加新的变量来判断数据结束和读入的数据。
wait_queue_head_t read_wait;
struct kfifo read_fifo;
int read_len;
对于mpsse应用,在读之前会写入命令,所以在写之前需要初始化好read_fifo, read_len这2个变量。
在ftdi_process_read_urb结束前添加
if((priv->bitmode == FTDI_SIO_BITMODE_MPSSE || priv->bitmode == FTDI_SIO_BITMODE_SYNC_BITBANG) && count > 0) {int offset = 2;while(count > 0) {int ret = 0;count += 2;//printk("%s: count=%d,offset=%d,read_len=%d\n", __func__, count, offset, priv->read_len);if(count >= priv->max_packet_size) {if(kfifo_initialized(&priv->read_fifo)) {ret = kfifo_in(&priv->read_fifo, data + offset, priv->max_packet_size - 2);}priv->read_len -= priv->max_packet_size - 2;count -= priv->max_packet_size;offset += priv->max_packet_size;} else {if(kfifo_initialized(&priv->read_fifo)) {ret = kfifo_in(&priv->read_fifo, data + offset, count - 2);}priv->read_len -= count - 2;offset += count;count = 0;}}if(priv->read_len <= 0) {priv->read_len = 0;if(kfifo_initialized(&priv->read_fifo))wake_up_interruptible(&priv->read_wait); // 唤醒等待队列 }}
设备每max_packet_size的前面2个字节是modem status,所以数据是从data[2]开始拷贝的。如果read_len变为0或小于0,就唤醒等待队列。例如FT230X的芯片max_packet_size为64字节,假设接收到124个字节数据,那么实际接收到的数据是128字节。数据结构是:
0x30 0x60 + 62字节数据 + 0x30 0x60 + 62字节数据
所以循环64字节把数据去掉2个字节压入FIFO中。
5. mpsse写gpio
static int ftdi_set_mpsse_pins(struct usb_serial_port *port)
把16个gpio的状态一次更新,不区分不同gpio。
static int ftdi_set_mpsse_pins(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);int result;u8 cmd[6];if(priv->mpsse_enable == false)return 0;cmd[0] = 0x80;cmd[1] = (u8)(priv->mpsse_gpio_value & 0xff);cmd[2] = (u8)(priv->mpsse_gpio_output & 0xff);cmd[3] = 0x82;cmd[4] = (u8)((priv->mpsse_gpio_value >> 8) & 0xff);cmd[5] = (u8)((priv->mpsse_gpio_output >> 8) & 0xff);result = ftdi_usb_write(port, cmd, true);return result;
}
6. mpsse读gpio
static int ftdi_get_mpsse_pins(struct usb_serial_port *port)
这个的返回值就是16个gpio的电平值。
mpsse读GPIO的命令是0x81和0x83,分别读入低字节和高字节GPIO状态。写完这2个命令字后读入2个字节数据。
首先初始化fifo
priv->read_len = 2;
if(kfifo_alloc(&priv->read_fifo, priv->read_len * 2, GFP_KERNEL)) {dev_info(&port->dev, "mpsse get pins read buffer malloc fail\n");return -ENOMEM;
}
然后发送2个字节的读GPIO命令
cmd[0] = 0x81; //Read AD0-AD7
cmd[1] = 0x83; //Read AC0-AC7
result = ftdi_usb_write(port, cmd, 2, false);
if(result < 0) {dev_info(&port->dev, "mpsse get pins fail(send command) %d\n", result);goto cleanup;
}
等待读入的数据,即2个字节数据
result = wait_event_interruptible_timeout(priv->read_wait, !priv->read_len, HZ / 10);if (result == -ERESTARTSYS) {result = -EINTR;// 信号中断睡眠goto cleanup;} else if (result < 0) { // 超时dev_err(&port->dev, "Read pending timeout\n");priv->read_len = 0; // 防止死循环result = -ETIMEDOUT;goto cleanup;}
最后把数据从fifo取出来,然后返回
result = kfifo_out(&priv->read_fifo, cmd, 2);result = (cmd[1] << 8) | cmd[0];cleanup:kfifo_free(&priv->read_fifo);return result;
7. ftdi_gpio_init
增加ftdi_mpsse_gpio_init_ft232h,由于FT232H是特例,既支持CBUS又支持MPSSE,所以把GPIO分为20个pin,前面4个为CBUS,后面16个是MPSSE。
static void ftdi_mpsse_gpio_init_ft232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gc.ngpio += 16;priv->mpsse_gpio_altfunc = 0x0000;
}
然后在ftdi_gpio_init_ft232h后面添加
case FT232H:
case FT232HP:
case FT233HP:result = ftdi_gpio_init_ft232h(port);ftdi_mpsse_gpio_init_ft232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);break;
同理,添加FT2232H和FT4232H的情况。
static void ftdi_mpsse_gpio_init_ft2232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gc.ngpio = 16;priv->mpsse_gpio_altfunc = 0x0000;
}static int ftdi_mpsse_gpio_init_ft4232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);if(priv->channel == CHANNEL_A || priv->channel == CHANNEL_B)priv->gc.ngpio = 8;else{priv->gc.ngpio = 0;return -EIO;}priv->mpsse_gpio_altfunc = 0x0000;return 0;
}case FT2232H:case FT2232HP:case FT2233HP:ftdi_mpsse_gpio_init_ft2232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);result = 0;break;case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:result = ftdi_mpsse_gpio_init_ft4232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);break;
注意FT4232H有点特殊,通道C和通道D不支持MPSSE。
编译安装驱动后可以看到gpio有20个了。
:~/Project/ftdi_sio$ cd /sys/class
:/sys/class$ cd gpio/gpiochip512
:/sys/class/gpio/gpiochip512$ ls
base device label ngpio power subsystem uevent
:/sys/class/gpio/gpiochip512$ cat base
512
:/sys/class/gpio/gpiochip512$ cat ngpio
20
8. ftdi_gpio_request
这一步是将GPIO暴露出来时执行的,是将芯片的模式切换到CBUS或MPSSE模式,而且只需要在申请第一个GPIO时处理设置模式。
首先把ftdi_private里面的gpio_used改一下,由原来的bool型改为int型,这样记录每个gpio是否使用。
int gpio_used; /* true if the user requested a gpio */
这个函数里面设置这个变量为true的地方移动到返回前(移动到if外),并且判断这个gpio是不是已经request了,如果是就返回EBUSY错误。
if(priv->gpio_used & (1 << offset))result = -EBUSY;
elsepriv->gpio_used = (1 << offset);
在ftdi_gpio_request里面修改,要分3种情况,
switch(priv->chip_type) {}
如果是FT232H/FT232HP/FT233HP,要区分CBUS和MPSSE模式。
case FT232H:
case FT232HP:
case FT233HP:if(offset < 4) {result = ftdi_set_cbus_pins(port);if (result) {goto fail_setmode;}} else {result = ftdi_set_mpsse_mode(port);if (result) {dev_info(&port->dev, "set mpsse mode fail %d\n", result);goto fail_setmode;}ftdi_set_mpsse_pins(port);}break;
就是将芯片切换到CBUS模式或者MPSSE模式。
如果是其他H系列的芯片则只要设置MPSSE模式。
case FT2232H:
case FT2232HP:
case FT2233HP:
case FT4232H:
case FT4232HP:
case FT4233HP:
case FT4232HA:result = ftdi_set_mpsse_mode(port);if (result) {goto fail_setmode;} ftdi_set_mpsse_pins(port);break;
其他芯片按照之前的方式
default:result = ftdi_set_cbus_pins(port);if (result) {goto fail_setmode;}break;
这里去掉下面那部分代码是因为使用gpiodev这类库时,程序每次控制gpio都会request或free,不去掉会每次控制其他GPIO。
priv->gpio_output = 0x00;
priv->gpio_value = 0x00;
9. ftdi_gpio_free
这个是新增加的,为了处理unexport所有gpio后,需要做一些处理。在ftdi_gpio_init中添加这个函数的赋值
priv->gc.free = ftdi_gpio_free;
该函数将unexport的gpio对应的位清0
static void ftdi_gpio_free(struct gpio_chip *gc, unsigned int offset)
{struct usb_serial_port *port = gpiochip_get_data(gc);struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gpio_used &= ~(BIT(offset));
}
10. ftdi_gpio_direction_get
这个函数是读取到当前gpio的方向,通过私有数据gpio_output(CBUS模式)或mpsse_gpio_output(MPSSE模式)返回对应的bit就可。
同样区分三种方式,后面所有的gpio控制函数都是这样的结构。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4)return !(priv->gpio_output & BIT(gpio));elsereturn !(priv->mpsse_gpio_output & BIT(gpio - 4));case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:return !(priv->mpsse_gpio_output & BIT(gpio));default:return !(priv->gpio_output & BIT(gpio));
}
11. ftdi_gpio_direction_input
这个函数是设置gpio的方向为输入,输入的设定值是0,同样,私有数据gpio_output(CBUS模式)或mpsse_gpio_output(MPSSE模式)设置对应的位即可。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {priv->gpio_output &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {priv->mpsse_gpio_output &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;}break; case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_output &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;break;default:priv->gpio_output &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}
12. ftdi_gpio_direction_output
这个函数是设置GPIO的方向为输出且输出电平。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {priv->gpio_output &= ~BIT(gpio);priv->gpio_output |= BIT(gpio);if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {priv->mpsse_gpio_output &= ~BIT(gpio - 4);priv->mpsse_gpio_output |= BIT(gpio - 4);if (value)priv->mpsse_gpio_value |= BIT(gpio - 4);elsepriv->mpsse_gpio_value &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;}break; case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_output &= ~BIT(gpio);priv->mpsse_gpio_output |= BIT(gpio);if (value)priv->mpsse_gpio_value |= BIT(gpio);elsepriv->mpsse_gpio_value &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;break;default:priv->gpio_output |= BIT(gpio);if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}
13. ftdi_gpio_init_valid_mask
这个函数用于初始化芯片的合法掩码。
unsigned long map = priv->gpio_altfunc;
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:map = (priv->gpio_altfunc & 0x0f) | (priv->mpsse_gpio_altfunc << 4);break; case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:map = priv->mpsse_gpio_altfunc;break;default:break;
}
如果altfunc对应的位为1,在系统里面这个GPIO就是被占用的。
14. ftdi_gpio_get
这个函数是读取对应gpio的电平值,即sysfs中value文件的值。同样,分3种情况读取gpio即可。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4){result = ftdi_read_cbus_pins(port);} else {mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);result <<= 4;}break;case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);break;default:result = ftdi_read_cbus_pins(port);break;
}
15. ftdi_gpio_set
对应ftdi_gpio_get,设置某个gpio的电平。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {if (value)priv->mpsse_gpio_value |= BIT(gpio - 4);elsepriv->mpsse_gpio_value &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);}break; case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:if (value)priv->mpsse_gpio_value |= BIT(gpio);elsepriv->mpsse_gpio_value &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);break;default:if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}
15. ftdi_gpio_get_multiple
功能类似ftdi_gpio_set,只是获取多个gpio。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:int mpsse = 0;result = 0;if(priv->bitmode != FTDI_SIO_BITMODE_MPSSE) {result = ftdi_read_cbus_pins(port);}else {mutex_lock(&priv->gpio_lock);mpsse = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);}result |= mpsse << 4;break;case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);break;default:result = ftdi_read_cbus_pins(port);break;
}
16. ftdi_gpio_set_multiple
功能类似ftdi_gpio_set,只是设置多个gpio。
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:unsigned long mask_mpsse = ((*mask) >> 4) & 0xffff;unsigned long bits_mpsse = ((*bits) >> 4) & 0xffff;if(priv->bitmode == FTDI_SIO_BITMODE_MPSSE) {priv->mpsse_gpio_value &= ~mask_mpsse;priv->mpsse_gpio_value |= bits_mpsse & mask_mpsse;ftdi_set_mpsse_pins(port);} else {priv->gpio_value &= ~mask_cbus;priv->gpio_value |= bits_cbus & mask_cbus;ftdi_set_cbus_pins(port);}break; case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_value &= ~(*mask);priv->mpsse_gpio_value |= *bits & *mask;ftdi_set_mpsse_pins(port);break;default:priv->gpio_value &= ~(*mask);priv->gpio_value |= *bits & *mask;ftdi_set_cbus_pins(port);break;
}
17. 验证
假设控制FT232H的ACBUS7,编号为base + 11,即前面4个为CBUS,所以MPSSE的AC7(第8个gpio)对应11。因为base = 512,所以该GPIO的编号为523。注意,在Ubuntu中要想操作gpio,需要root下执行命令。
:/sys/class/gpio# echo 523 > /sys/class/gpio/export
:/sys/class/gpio# ls
export gpio523 gpiochip512 unexport
:/sys/class/gpio# echo "out" >/sys/class/gpio/gpio523/direction
:/sys/class/gpio# echo 1 > gpio523/value
:/sys/class/gpio# echo 0 > gpio523/value
:/sys/class/gpio# cat gpio523/value
0
:/sys/class/gpio# gpioinfo 0
gpiochip0 - 20 lines:line 0: unnamed kernel input active-high [used]line 1: unnamed kernel input active-high [used]line 2: unnamed kernel input active-high [used]line 3: unnamed kernel input active-high [used]line 4: unnamed unused input active-high line 5: unnamed unused input active-high line 6: unnamed unused input active-high line 7: unnamed unused input active-high line 8: unnamed unused input active-high line 9: unnamed unused input active-high line 10: unnamed unused input active-high line 11: unnamed unused input active-high line 12: unnamed unused input active-high line 13: unnamed unused input active-high line 14: unnamed unused input active-high line 15: unnamed unused input active-high line 16: unnamed unused input active-high line 17: unnamed unused input active-high line 18: unnamed unused input active-high line 19: unnamed unused input active-high