目录
2.3.3 在ft260_uart_device_list链表中查找空闲设备并添加设备
2.4 tty_port_register_device_attr
上一节中讲到ft260_probe里面会根据接口的类型做不同的probe,这里详细了解一下uart部分是如何初始化的。
1. wakeup任务
FT260支持省电模式,在这种模式下,如果FT260在5秒内没有数据,芯片会切换系统频率到30KHz以节省电能。该功能是默认开启的。
当使用uart时,如果波特率大于4800,芯片从空闲状态返回工作状态时会导致RX线路丢失数据,为了不用编程关闭这个功能,驱动程序作者设计了一个定时功能关闭FT260的省电模式,即至少每4.8秒发送一次伪报告(读取芯片版本的命令)以避免芯片进入省电模式。
INIT_WORK(&dev->wakeup_work, ft260_uart_do_wakeup);
ft260_uart_wakeup_workaraund_enable(dev, true);
/* Work not started at this point */
timer_setup(&dev->wakeup_timer, ft260_uart_start_wakeup, 0);
INIT_WORK
是一个宏,用于初始化一个工作队列项(work
)。&dev->wakeup_work
是指向设备结构体(dev
)中的wakeup_work
成员的指针,ft260_uart_do_wakeup
是一个函数指针,指向处理唤醒操作的具体函数(ft260_uart_do_wakeup
),内部只是读取了一下芯片版本号(就是为了通信一次避免进入省电模式)
static void ft260_uart_do_wakeup(struct work_struct *work)
{
struct ft260_device *dev =
container_of(work, struct ft260_device, wakeup_work);
ft260_uart_wakeup(dev);
}
container_of
宏将work
结构体指针转换为struct ft260_device
类型的指针dev
。container_of
宏用于在Linux内核中查找包含某个成员的结构体的地址。 该例中wakeup_wrok是结构体ft260_device的成员变量,而参数work指向的数据即是这个变量,所以通过work的地址就可以计算得到ft260_device这个指针地址。
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
ft260_uart_wakeup_workaraund_enable就是根据芯片是否配置了power_saving_en来决定是否开启wakeup机制。
static void ft260_uart_wakeup_workaraund_enable(struct ft260_device *port,
bool enable)
{
if (port->power_saving_en) {
port->reschedule_work = enable;
ft260_dbg("%s wakeup workaround",
enable ? "activate" : "deactivate");
}
}
timer_setup用于设置一个定时器,使其在指定的时间后触发一个事件。ft260_uart_start_wakeup是定时器回调函数,用户数据参数为0。这里定时器并不会启动。
2. 初始化tty端口
2.1 tty_port_init
tty_port_init(&dev->port);
tty_port_init
函数是Linux内核中的一个函数,用于初始化tty_port
结构体。这个结构体封装了与终端(tty)设备相关的状态和操作,如缓冲区管理、锁和其他控制信息。函数的主要作用是设置端口的初始状态,包括初始化必要的数据结构和同步原语,比如互斥锁,确保后续对tty设备的安全访问。简而言之,它准备了一个tty设备驱动程序使用的通信端口的上下文。
2.2 ft260_uart_port_ops
static const struct tty_port_operations ft260_uart_port_ops = {
.shutdown = ft260_uart_port_shutdown,
.activate = ft260_uart_port_activate,
.destruct = ft260_uart_port_destroy,
};
它用于描述一个UART(通用异步收发传输器)端口的操作。tty_port_operations结构体定义如下:
struct tty_port_operations {
bool (*carrier_raised)(struct tty_port *port);
void (*dtr_rts)(struct tty_port *port, bool active);
void (*shutdown)(struct tty_port *port);
int (*activate)(struct tty_port *port, struct tty_struct *tty);
void (*destruct)(struct tty_port *port);
};
- carrier_raised:这个函数指针表示检查载波是否已经升起。它接受一个
tty_port
类型的指针作为参数,并返回一个bool
类型的值。当调用这个函数时,如果载波已经升起,返回true
;否则返回false
。 - dtr_rts:这个函数指针用于控制数据终端准备好(DTR)和请求发送(RTS)信号的状态。它接受一个
tty_port
类型的指针和一个bool
类型的active
参数。当active
为true
时,表示要使能DTR和RTS信号;当active
为false
时,表示要禁用DTR和RTS信号。 - shutdown: 这个函数指针用于关闭tty端口。它接受一个
tty_port
类型的指针作为参数。当调用这个函数时,会执行关闭端口的操作。 - activate: 这个函数指针用于激活tty端口。它接受一个
tty_port
类型的指针和一个tty_struct
类型的指针作为参数,并返回一个int
类型的值。当调用这个函数时,会执行激活端口的操作,并返回操作结果。 - destruct: 这个函数指针用于销毁tty端口。它接受一个
tty_port
类型的指针作为参数。当调用这个函数时,会执行销毁端口的操作。
2.3 ft260_uart_add_port
ret = ft260_uart_add_port(dev);
if (ret) {
hid_err(hdev, "failed to add port\n");
return ret;
}
这一步是将ft260这个设备添加到ft260_uart_device_list链表中。
static LIST_HEAD(ft260_uart_device_list);
LIST_HEAD
是一个宏,它通常在Linux内核中用于创建双向链表的头部。
2.3.1 初始化发送FIFO锁
spin_lock_init(&port->xmit_fifo_lock);
通过调用spin_lock_init
函数,将port->xmit_fifo_lock
变量初始化为一个自旋锁,以便在后续的代码中使用。
2.3.2 分配一个传输FIFO
if (kfifo_alloc(&port->xmit_fifo, XMIT_FIFO_SIZE, GFP_KERNEL))
return -ENOMEM;
2.3.3 在ft260_uart_device_list链表中查找空闲设备并添加设备
mutex_lock(&ft260_uart_list_lock);
list_for_each_entry(dev, &ft260_uart_device_list, device_list) {
if (dev->index != index)
break;
index++;
}
port->index = index;
list_add(&port->device_list, &ft260_uart_device_list);
mutex_unlock(&ft260_uart_list_lock);
- 加锁保护ft260_uart_device_list链表。这个锁是静态全局变量
static DEFINE_MUTEX(ft260_uart_list_lock);
- 遍历链表中的设备,如果当前设备的index不等于待添加设备的index,则跳出循环。
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
- 更新待添加设备的index。
- 将待添加设备添加到链表尾部。
- 解锁保护链表。
2.4 tty_port_register_device_attr
devt = tty_port_register_device_attr(&dev->port,
ft260_tty_driver,
dev->index, &hdev->dev,
dev, NULL);
tty_port_register_device_attr用于在TTY系统中注册一个设备节点,并将其与相应的驱动程序关联起来。该函数的参数依次为:
- dev->port:表示要注册的TTY设备的端口结构体。
- ft260_tty_driver:表示要注册的TTY设备的驱动程序。
- dev->index: 表示要注册的TTY设备的索引号。
- &hdev->dev: 表示要注册的TTY设备的硬件设备结构体。
- dev: 表示要注册的TTY设备的私有数据结构体。
- NULL: 表示该设备没有额外的属性。
该函数的返回值保存在devt
变量中,它表示注册的设备节点的设备类型。
3. 设置FT260串口默认参数
req.report = FT260_SYSTEM_SETTINGS;
req.request = FT260_SET_UART_CONFIG;
req.flow_ctrl = FT260_UART_CFG_FLOW_CTRL_NONE;
put_unaligned_le32(cpu_to_le32(9600), &req.baudrate);
req.data_bit = FT260_UART_CFG_DATA_BITS_8;
req.parity = FT260_UART_CFG_PAR_NO;
req.stop_bit = FT260_UART_CFG_STOP_ONE_BIT;
req.breaking = FT260_UART_CFG_BREAKING_NO;
ret = ft260_hid_feature_report_set(hdev, (u8 *)&req, sizeof(req));
if (ret < 0) {
hid_err(hdev, "failed to configure uart: %d\n", ret);
goto err_hid_report;
}
这部分可以参考FT260的AN_394文档中的4.4.18 Configure UART。
4. gpio初始化
FT260有2个interface,第一个interface0控制i2c,同时也控制gpio,另一个interface1控制uart。
if (dev->iface_id == 0) {
ret = ft260_gpio_init(dev, cfg);
if (ret)
goto err_hid_report;
ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
if (ret < 0) {
hid_err(hdev, "failed to create sysfs attrs\n");
goto err_hid_report;
}
}
4.1 ft260_gpio_init
static int ft260_gpio_init(struct ft260_device *dev,
struct ft260_get_system_status_report *cfg)
uart支持5种不同的模式,不同的模式会影响到gpio的分配使用。
u8 uart_mode; /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
/* 3 - XON_XOFF, 4 - No flow control */
dev->gpio_uart_mode[0] = (u16)FT260_GPIO_UART_MODE_0_SET;
dev->gpio_uart_mode[1] = (u16)FT260_GPIO_UART_MODE_1_SET;
dev->gpio_uart_mode[2] = (u16)FT260_GPIO_UART_MODE_2_SET;
dev->gpio_uart_mode[3] = (u16)FT260_GPIO_UART_MODE_3_SET;
dev->gpio_uart_mode[4] = (u16)FT260_GPIO_UART_MODE_4_SET;
当芯片的chip_mode(实际是管脚DCNF0和DCNF1的状态),这里的判断似乎有问题,如果要不改动这段代码,硬件上应该舍弃mode0的情况(mode3的功能是等于mode0的)。
if (cfg->chip_mode) {
if (cfg->chip_mode & FT260_MODE_UART || cfg->chip_mode == FT260_MODE_ALL)
dev->gpio_en |= dev->gpio_uart_mode[cfg->uart_mode];
else
dev->gpio_en |= FT260_GPIO_UART_DEFAULT;
if (!(cfg->chip_mode & FT260_MODE_I2C))
dev->gpio_en |= FT260_GPIO_I2C_DEFAULT;
}
然后将gpio_en对应的GPIO使能。注意,这里GPIO的使能是和UART或I2C对应功能使能相反的。比如当UART模式为OFF时,模式FT260_GPIO_UART_MODE_0_SET的定义:
FT260_GPIO_UART_RX_TX = (FT260_GPIO_C | FT260_GPIO_D),
FT260_GPIO_UART_DCD_RI = (FT260_GPIO_4 | FT260_GPIO_5),
FT260_GPIO_UART_RTS_CTS = (FT260_GPIO_B | FT260_GPIO_E),
FT260_GPIO_UART_DTR_DSR = (FT260_GPIO_F | FT260_GPIO_H),
FT260_GPIO_UART_MODE_0_SET = (FT260_GPIO_UART_RX_TX |
FT260_GPIO_UART_DCD_RI |
FT260_GPIO_UART_RTS_CTS |
FT260_GPIO_UART_DTR_DSR),
就是把GPIOC/GPIOD/GPIO4/GPIO5/GPIOB/GPIOE/GPIOF/GPIOH这些管脚都设置为GPIO。
另外4个GPIO(GPIO2/GPIO3/GPIOA/GPIOG)根据芯片的配置设置使能。
if (cfg->gpio2_func == FT260_MFPIN_GPIO)
dev->gpio_en |= FT260_GPIO_2;
if (cfg->enable_wakeup_int == FT260_MFPIN_GPIO)
dev->gpio_en |= FT260_GPIO_3;
if (cfg->gpioa_func == FT260_MFPIN_GPIO)
dev->gpio_en |= FT260_GPIO_A;
if (cfg->gpiog_func == FT260_MFPIN_GPIO)
dev->gpio_en |= FT260_GPIO_G;
动态分配一个gpio chip内存空间
dev->gc = devm_kzalloc(&hdev->dev, sizeof(*dev->gc), GFP_KERNEL);
if (!dev->gc)
return -ENOMEM;
为FT260 GPIO初始化一个标签
label_sz = strlen(dev_name(&hdev->dev)) + strlen(prefix) + 1;
label = devm_kzalloc(&hdev->dev, label_sz, GFP_KERNEL);
if (!label) {
ret = -ENOMEM;
goto exit;
}
snprintf(label, label_sz, "%s%s", prefix, dev_name(&hdev->dev));
将GPIO添加到设备管理器中,不知道为什么中间会读一下芯片版本信息以唤醒芯片。
dev->gc->label = label;
dev->gc->direction_input = ft260_gpio_direction_input;
dev->gc->direction_output = ft260_gpio_direction_output;
dev->gc->get_direction = ft260_gpio_get_direction;
dev->gc->set = ft260_gpio_set;
dev->gc->get = ft260_gpio_get;
dev->gc->base = -1;
dev->gc->ngpio = FT260_GPIO_TOTAL;
dev->gc->can_sleep = true;
dev->gc->parent = &hdev->dev;
/* Wakeup chip */
(void)ft260_hid_feature_report_get(dev->hdev, FT260_CHIP_VERSION,
(u8 *)&ver, sizeof(ver));
ret = devm_gpiochip_add_data(&hdev->dev, dev->gc, dev);
if (ret < 0)
hid_err(hdev, "cannot add GPIO chip %d\n", ret);
4.2 sysfs_create_group
ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
if (ret < 0) {
hid_err(hdev, "failed to create sysfs attrs\n");
goto err_i2c_free;
}
这段代码的功能是在sysfs中创建一个名为ft260_attr_group的属性组。这个设置会让系统在sysfs空间内产生多个属性文件。
static const struct attribute_group ft260_attr_group = {
.attrs = (struct attribute *[]) {
&dev_attr_chip_mode.attr,
&dev_attr_pwren_status.attr,
&dev_attr_suspend_status.attr,
&dev_attr_hid_over_i2c_en.attr,
&dev_attr_power_saving_en.attr,
&dev_attr_i2c_enable.attr,
&dev_attr_gpio2_func.attr,
&dev_attr_gpioa_func.attr,
&dev_attr_gpiog_func.attr,
&dev_attr_uart_mode.attr,
&dev_attr_uart_dcd_ri.attr,
&dev_attr_clock_ctl.attr,
&dev_attr_i2c_reset.attr,
&dev_attr_clock.attr,
NULL
}
};
在系统中的显示如下:
/sys/bus/usb/devices/1-2/1-2:1.0/0003:0403:6030.0002$ ls
chip_mode clock_ctl gpio gpioa_func gpiog_func i2c-1 i2c_reset power pwren_status subsystem uart_dcd_ri uevent
clock driver gpio2_func gpiochip0 hid_over_i2c_en i2c_enable modalias power_saving_en report_descriptor suspend_status uart_mode