hid-ft260驱动学习笔记 3 - ft260_uart_probe

目录

1. wakeup任务

2. 初始化tty端口

2.1 tty_port_init

2.2 ft260_uart_port_ops

2.3 ft260_uart_add_port

2.3.1 初始化发送FIFO锁

2.3.2 分配一个传输FIFO

2.3.3 在ft260_uart_device_list链表中查找空闲设备并添加设备

2.4 tty_port_register_device_attr

3. 设置FT260串口默认参数

4. gpio初始化

4.1 ft260_gpio_init

4.2 sysfs_create_group


上一节中讲到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类型的指针devcontainer_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参数。当activetrue时,表示要使能DTR和RTS信号;当activefalse时,表示要禁用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

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值