目录
12. ftdi_gpio_direction_output
之前GPIO是基于CBUS的模式,对于H系列芯片还支持MPSSE模式。注意CBUS模式和MPSSE模式不能同时使用。基本是根据CBUS模式修改。
1. ftdi_private
一路MPSSE最多支持16个GPIO,所以在ftdi_private里面增加3个u16的变量来表示这些gpio的属性。
#ifdef CONFIG_GPIOLIB
struct 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;
else
priv->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));
else
return !(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);
else
priv->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);
else
priv->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);
else
priv->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);
else
priv->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);
else
priv->gpio_value &= ~BIT(gpio);
result = ftdi_set_cbus_pins(port);
} else {
if (value)
priv->mpsse_gpio_value |= BIT(gpio - 4);
else
priv->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);
else
priv->mpsse_gpio_value &= ~BIT(gpio);
result = ftdi_set_mpsse_pins(port);
break;
default:
if (value)
priv->gpio_value |= BIT(gpio);
else
priv->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