目录
1. ftdi_port_probe
1.1 私有数据结构ftdi_private
1.2 特殊probe处理
1.3 确定FTDI设备类型
1.4 确定最大数据包大小
1.5 设置读取延迟时间
1.6 初始化GPIO
1.6.1 使能GPIO
1.6.2 添加到系统
1.6.2.1 设置GPIO控制器的基本信息
1.6.2.2 设置GPIO控制器的元信息
1.6.3 GPIO实例
2. ftdi_gpio_remove
int (*port_probe)(struct usb_serial_port *port): 端口探测函数ftdi_port_probe,用于初始化单个端口。
void (*port_remove)(struct usb_serial_port *port): 端口移除函数ftdi_port_remove
ftdi_port_remove,用于清理单个端口。
1. ftdi_port_probe
1.1 私有数据结构ftdi_private
这个结构是FTDI设备特有的,其实就是用来在驱动中传递参数用的。在这个结构体中定义了FTDI设备私有的一些变量。
struct ftdi_private *priv;
priv = kzalloc(sizeof(struct ftdi_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
这个私有数据是通过usb_set_serial_port_data放到port的私有数据保存。
usb_set_serial_port_data(port, priv);
1.2 特殊probe处理
和上一节一样,端口的probe处理也是有特殊的情况。这个结构体是通过port->serial传递的。
const struct ftdi_quirk *quirk = usb_get_serial_data(port->serial);
if (quirk && quirk->port_probe)
quirk->port_probe(priv);
1.3 确定FTDI设备类型
result = ftdi_determine_type(port);
if (result)
goto err_free;
该类型是通过设备描述符中的bcdDevice来区别的。
version = le16_to_cpu(udev->descriptor.bcdDevice);
以FT4232H为例,该值可以在设备属性的硬件ID中查看到,如下图REV_0800
case 0x800:
priv->chip_type = FT4232H;
break;
通过当前接口初始化通道编号,这只对多串口有意义(比如FT2232H和FT4232H)
#define CHANNEL_A 1
#define CHANNEL_B 2
#define CHANNEL_C 3
#define CHANNEL_D 4
ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
priv->channel = CHANNEL_A + ifnum;
而baud_base是指波特率产生器的参考时钟,默认设置的是H系列的值:120M的二分频。
priv->baud_base = 120000000 / 2;
最后是向设备节点发送消息,表明检测到了哪种类型的FTDI芯片。
dev_info(&udev->dev, "Detected %s\n", ftdi_chip_name[priv->chip_type]);
在命令“sudo dmesg”返回结果中可以找到这句信息内容,例如:
[16005.322690] usb 2-1: Detected FT4232H
1.4 确定最大数据包大小
ftdi_set_max_packet_size(port);
这是从设备的端点描述符中获取设备端点最大数据包大小。这一步更像是检查FT232R的端点数据包最大值被客制为0的情况,当被改为0时数据包大小设置为64字节。
1.5 设置读取延迟时间
if (read_latency_timer(port) < 0)
priv->latency = 16;
write_latency_timer(port);
这段代码的作用是确保设备的读取延迟时间被正确设置,如果无法获取当前的延迟时间,则会使用一个默认值进行设置。
获取这个参数是通过函数usb_control_msg_recv实现的。
int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request,
__u8 requesttype, __u16 value, __u16 index,
void *driver_data, __u16 size, int timeout,
gfp_t memflags)
对应的调用:
rv = usb_control_msg_recv(udev, 0, FTDI_SIO_GET_LATENCY_TIMER_REQUEST,
FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE, 0,
priv->channel, &buf, 1, WDR_TIMEOUT,
GFP_KERNEL);
其中参数request和requesttype在ftdi_sio.h中有定义:
#define FTDI_SIO_GET_LATENCY_TIMER 0x0a /* Get the latency timer */
#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER
#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0
这属于FTDI定义的命令。
同样,写这个参数的命令是:
#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */
#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER
#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40
rv = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
l, priv->channel,
NULL, 0, WDR_TIMEOUT);
1.6 初始化GPIO
这部分是利用了FTDI的CBUS实现USB转GPIO的功能。这一步是受内核配置CONFIG_GPIOLIB控制的,如果没有使能GPIOLIB,则没有这部分功能。
1.6.1 使能GPIO
由于不是所有的FTDI芯片都支持CBUS功能,所以前面有做芯片类型的判断,这里会引用这个变量分别处理。
switch (priv->chip_type) {
case FT232H:
result = ftdi_gpio_init_ft232h(port);
break;
case FT232R:
result = ftdi_gpio_init_ft232r(port);
break;
case FTX:
result = ftdi_gpio_init_ftx(port);
break;
default:
return 0;
}
只有3类芯片支持CBUS功能,这里3个分支都是从芯片(或外置EEPROM)中读取CBUS配置信息使能CBUS脚。例如FT232H,一共有4个CBUS脚,分别为AC5,AC6,AC8和AC9,从eeprom的地址0x1a读入4个字节
ret = ftdi_read_eeprom(port->serial, buf, 0x1a, 4);
if (ret < 0)
goto out_free;
每个GPIO由4位表示方向和电平。
/*
* FT232H CBUS Memory Map
*
* 0x1a: X- (upper nibble -> AC5)
* 0x1b: -X (lower nibble -> AC6)
* 0x1c: XX (upper nibble -> AC9 | lower nibble -> AC8)
*/
cbus_config = buf[2] << 8 | (buf[1] & 0xf) << 4 | (buf[0] & 0xf0) >> 4;
最后更新priv->gc.ngpio和gpio_altfunc,对应GPIO使能的话,gpio_altfunc对应的位清零
priv->gc.ngpio = 4;
priv->gpio_altfunc = 0xff;
for (i = 0; i < priv->gc.ngpio; ++i) {
if ((cbus_config & 0xf) == FTDI_FTX_CBUS_MUX_GPIO)
priv->gpio_altfunc &= ~BIT(i);
cbus_config >>= 4;
}
1.6.2 添加到系统
这一步是通过标准的gpiochip的驱动API函数gpiochip_add_data实现。
priv->gc.label = "ftdi-cbus";
priv->gc.request = ftdi_gpio_request;
priv->gc.get_direction = ftdi_gpio_direction_get;
priv->gc.direction_input = ftdi_gpio_direction_input;
priv->gc.direction_output = ftdi_gpio_direction_output;
priv->gc.init_valid_mask = ftdi_gpio_init_valid_mask;
priv->gc.get = ftdi_gpio_get;
priv->gc.set = ftdi_gpio_set;
priv->gc.get_multiple = ftdi_gpio_get_multiple;
priv->gc.set_multiple = ftdi_gpio_set_multiple;
priv->gc.owner = THIS_MODULE;
priv->gc.parent = &serial->interface->dev;
priv->gc.base = -1;
priv->gc.can_sleep = true;
result = gpiochip_add_data(&priv->gc, port);
if (!result)
priv->gpio_registered = true;
第一个参数priv->gc是配置好的`gpio_chip`结构体,第二个参数是该gpio_chip的私有数据,即实现接口参数的传递,例如:
static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset)
{
struct usb_serial_port *port = gpiochip_get_data(gc);
1.6.2.1 设置GPIO控制器的基本信息
- priv->gc.label: 设置GPIO控制器的标签为"ftdi-cbus"。
- priv->gc.request: 设置请求GPIO引脚的函数为`ftdi_gpio_request`,用于在使用前申请GPIO资源。
- priv->gc.get_direction: 设置获取GPIO引脚方向(输入或输出)的函数为`ftdi_gpio_direction_get`。
- priv->gc.direction_input: 设置将GPIO引脚配置为输入模式的函数为`ftdi_gpio_direction_input`。
- priv->gc.direction_output: 设置将GPIO引脚配置为输出模式的函数为`ftdi_gpio_direction_output`。
- priv->gc.init_valid_mask: 设置初始化有效掩码的函数为`ftdi_gpio_init_valid_mask`,用于确定哪些GPIO引脚可以被使用。
- priv->gc.get: 设置读取GPIO引脚状态的函数为`ftdi_gpio_get`。
- priv->gc.set: 设置设置GPIO引脚状态的函数为`ftdi_gpio_set`。
- priv->gc.get_multiple: 设置批量读取GPIO引脚状态的函数为`ftdi_gpio_get_multiple`。
- priv->gc.set_multiple: 设置批量设置GPIO引脚状态的函数为`ftdi_gpio_set_multiple`。
1.6.2.2 设置GPIO控制器的元信息
- priv->gc.owner: 设置拥有者为当前模块,表明这个GPIO控制器是由当前驱动程序管理的。
- priv->gc.parent: 设置父设备为`serial->interface->dev`,这通常是一个USB设备或串口设备,表明GPIO控制器与之关联。
- priv->gc.base: 设置GPIO引脚的起始编号为-1,实际值将在注册时由系统分配。
- priv->gc.can_sleep: 设置为`true`表示这个GPIO控制器可以在睡眠状态下工作。
1.6.3 GPIO实例
例如使用FT232H,使用FT_PROG设置AC9为I/O Mode
在Linux中可以看到多一个gpiochip512的文件夹
/sys/class/gpio$ ls
export gpiochip512 unexport
/sys/class/gpio/gpiochip512$ ls
base device label ngpio power subsystem uevent
/sys/class/gpio/gpiochip512$ cat label
ftdi-cbus
/sys/class/gpio/gpiochip512$ cat ngpio
4
/sys/class/gpio/gpiochip512$ cat base
512
可以看到一共分配了4个gpio,因为AC9是第四个GPIO,所以其GPIO编号应该为512 + 3 = 515.
/sys/class/gpio# echo "515" > /sys/class/gpio/export
/sys/class/gpio# ls
export gpio515 gpiochip512 unexport
注意,这里FT_PROG虽然只配置了一个GPIO,但是驱动层还是按照4个GPIO分配,如果暴露512可以看到返回错误:
/sys/class/gpio# echo "512" > /sys/class/gpio/export
bash: echo: 写入错误:无效的参数
2. ftdi_gpio_remove
这个函数将gpio移除和释放内存。