概述

****在mtk6769-A15-kernel-6.6平台,介绍upm7610 pd芯片驱动,添加pd芯片驱动(与ap使用i2c通信)主要是解决使用pd快充方案的需求,与pd充电器实现pd协议通信,达到升压提升充电功率目的。

1.upm7610_i2c_probe函数

~~分析驱动我们从upm7610_i2c_probe开始,一般就是先I2C 适配器(I2C Adapter)是否支持特定的通信功能,获取chip_id,解析设备树,寄存器参数初始化、Type-C 端口控制器初始化、中断告警初始化。

static int upm7610_i2c_probe(struct i2c_client *client)
{
	struct upm7610_chip *chip;
	int ret = 0, chip_id;
	bool use_dt = client->dev.of_node;

	pr_info("%s (%s)\n", __func__, UPM7610_DRV_VERSION);
	/* 检查i2c适配器是否支持块传输及单字节读取 */
	if (i2c_check_functionality(client->adapter,
			I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA))
		pr_info("I2C functionality : OK...\n");
	else
		pr_info("I2C functionality check : failuare...\n");

	/* 获取chip_id */
	chip_id = upm7610_check_revision(client);
	if (chip_id < 0)
		return chip_id;

	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	if (use_dt) {
		/*解析设备树*/
		ret = up_parse_dt(chip, &client->dev);	
		if (ret < 0)
			return ret;
	} else {
		dev_err(&client->dev, "no dts node\n");
		return -ENODEV;
	}
	chip->dev = &client->dev;
	chip->client = client;
	i2c_set_clientdata(client, chip);
	chip->chip_id = chip_id;
	pr_info("upm7610_chipID = 0x%0x\n", chip_id);
	mutex_init(&chip->irq_lock);
	chip->is_suspended = false;
	chip->irq_while_suspended = false;
	chip->vconn_en = false;
	chip->lpm_en = false;
	/* 寄存器初始化 */
	ret = upm7610_regmap_init(chip);
	if (ret < 0) {
		dev_err(chip->dev, "upm7610 regmap init fail\n");
		goto err_regmap_init;
	}
	/* Type-c port control初始化 */
	ret = upm7610_tcpcdev_init(chip, &client->dev);
	if (ret < 0) {
		dev_err(&client->dev, "upm7610 tcpc dev init fail\n");
		goto err_tcpc_reg;
	}
	/* 中断告警初始化 */
	ret = upm7610_init_alert(chip->tcpc);
	if (ret < 0) {
		pr_err("upm7610 init alert fail\n");
		goto err_irq_init;
	}

	pr_info("%s probe OK!\n", __func__);
	return 0;

err_irq_init:
	tcpc_device_unregister(chip->dev, chip->tcpc);
err_tcpc_reg:
	upm7610_regmap_deinit(chip);
err_regmap_init:
	mutex_destroy(&chip->irq_lock);
	return ret;
}

这里注重看设备树配置、Type-c port control初始化、中断告警初始化,其他都没啥东西。

2.dts配置

&i2c4 {
	upm7610_typec: upm7610@60 {
		compatible = "unisemipower,upm7610";	/* 驱动匹配标签 */
		reg = <0x60>;	    /* 芯片i2c地址 */
		status = "okay";	/* 设备树配置使能 */
		up-tcpc,name = "type_c_port0"; /* type-c端口控制器名字 */
		up-tcpc,role-def = <5>; /* 0: Unknown, 1: SNK, 2: SRC  配置控制器角色*/
							    /* 3: DRP, 4: Try.SRC, 5: Try.SNK */
		up-tcpc,rp-level = <0>; /* 0: Default, 1: 1.5, 2: 3.0 供电能力*/
		up-tcpc,vconn-supply = <1>;  /* 0: Never, 1: Always, */
					     /* 2: EMarkOnly, 3: StartOnly */
		upm7610pd,intr-gpio = <&pio 41 0x0>;	/* gpio中断配置 */
		upm7610pd,intr-gpio-num = <41>;
		/* charger = <&mt6375_chg>; */
		pd-data {
			pd,vid = <0x362f>;
			pd,pid = <0x7610>;
			/* 能够提供的供电能力 */
			pd,source-cap-ext = /bits/ 8 <0xcf 0x29 0x11 0x17
						      0x00 0x00 0x00 0x00
						      0x00 0x00 0x00 0x00
						      0x00 0x00 0x00 0x00
						      0x00 0x00 0x00 0x00
						      0x00 0x00 0x01 0x02
						      0x00>;
			/* 能够接受的受电能力 */
			pd,sink-cap-ext = /bits/ 8 <0xcf 0x29 0x11 0x17
						    0x00 0x00 0x00 0x00
						    0x00 0x00 0x01 0x00
						    0x00 0x00 0x00 0x00
						    0x01 0x0b 0x01 0x0a
						    0x0a 0x00 0x00 0x00>;
			pd,mfrs = "UnisemipowerTCPC";

			/*
			 *	VSAFE5V = 0, MAX_POWER = 1, CUSTOM = 2,
			 *	MAX_POWER_LV = 0x21, MAX_POWER_LVIC = 0x31
			 *	MAX_POWER_HV = 0x41, MAX_POWER_HVIC = 0x51
			 */
			pd,charging-policy = <0x31>;
			pd,source-pdo-size = <1>;
			pd,source-pdo-data = <0x00019032>; /*端口数据输出*/
			pd,sink-pdo-size = <1>;
			pd,sink-pdo-data = <0x000190c8>;

			pd,id-vdo-size = <6>;
			pd,id-vdo-data = <0xd140362f 0x0 0x76100000
					  0x61000000 0x0 0x41000000>;

			bat,nr = <1>;
			pd,country-nr = <0>;

			bat-info0 {
				bat,vid = <0x362f>;
				bat,pid = <0x7610>;
				bat,mfrs = "bat1";
				bat,design-cap = <3000>;
			};
		};
		dpm-caps {
			local-dr-power;
			local-dr-data;
			// local-ext-power;
			local-usb-comm;
			// local-usb-suspend;
			// local-high-cap;
			// local-give-back;
			local-no-suspend;

			/* With DP Source capability */
			// attempt-enter-dp-mode;
			// attempt-discover-cable;
			// attempt-discover-id;
			// attempt-discover-svids;

			/* 0: disable, 1: prefer-snk, 2: prefer-src */
			pr-check = <0>;
			// pr-reject-as-source;
			// pr-reject-as-sink;
			// pr-check-gp-source;
			// pr-check-gp-sink;

			/* 0: disable, 1: prefer-ufp, 2: prefer-dfp */
			dr-check = <0>;
			// dr-reject-as-dfp;
			// dr-reject-as-ufp;
		};
		displayport {
			/* connection type = "both", "ufp-d", "dfp-d" */
			1st-connection = "dfp-d";
			2nd-connection = "dfp-d";
			// usbr20-not-used;
			typec,receptacle;
			ufp-d {
				// pin-assignment,mode-c;
				// pin-assignment,mode-d;
				// pin-assignment,mode-e;
			};
			dfp-d {
				pin-assignment,mode-c;
				pin-assignment,mode-d;
				pin-assignment,mode-e;
			};
		};
	};
};

3. 解析 upm7610_tcpcdev_init 函数

****这里边比较重要的函数就是tcpc_device_register跟tcpci核心层建立联系,这个函数实现在tcpci_core.c中,将Type-C端口控制器物理层的操作方法集注册到核心层。

static int upm7610_tcpcdev_init(struct upm7610_chip *chip, struct device *dev)
{
	struct tcpc_desc *desc;
	struct tcpc_device *tcpc = NULL;
	struct device_node *np = dev->of_node;
	u32 val, len;
	const char *name = "default";

	dev_info(dev, "%s\n", __func__);

	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
	if (!desc)
		return -ENOMEM;
	/*type-c角色*/
	if (of_property_read_u32(np, "up-tcpc,role-def", &val) >= 0 ||
	    of_property_read_u32(np, "up-tcpc,role_def", &val) >= 0) {
		if (val >= TYPEC_ROLE_NR)
			desc->role_def = TYPEC_ROLE_DRP;
		else
			desc->role_def = val;
	} else {
		dev_info(dev, "use default Role DRP\n");
		desc->role_def = TYPEC_ROLE_DRP;
	}

	if (of_property_read_u32(np, "up-tcpc,rp-level", &val) >= 0 ||
	    of_property_read_u32(np, "up-tcpc,rp_level", &val) >= 0) {
		switch (val) {
		case TYPEC_RP_DFT:
		case TYPEC_RP_1_5:
		case TYPEC_RP_3_0:
			desc->rp_lvl = val;
			break;
		default:
			desc->rp_lvl = TYPEC_RP_DFT;
			break;
		}
	}

	if (of_property_read_u32(np, "up-tcpc,vconn-supply", &val) >= 0 ||
	    of_property_read_u32(np, "up-tcpc,vconn_supply", &val) >= 0) {
		if (val >= TCPC_VCONN_SUPPLY_NR)
			desc->vconn_supply = TCPC_VCONN_SUPPLY_ALWAYS;
		else
			desc->vconn_supply = val;
	} else {
		dev_info(dev, "use default VconnSupply\n");
		desc->vconn_supply = TCPC_VCONN_SUPPLY_ALWAYS;
	}

	if (of_property_read_string(np, "up-tcpc,name",
				(char const **)&name) < 0) {
		dev_info(dev, "use default name\n");
	}

	len = strlen(name);
	desc->name = kzalloc(len+1, GFP_KERNEL);
	if (!desc->name)
		return -ENOMEM;

	strlcpy((char *)desc->name, name, len+1);

	chip->tcpc_desc = desc;
	/*非常重要*/
	tcpc = tcpc_device_register(dev, desc, &upm7610_tcpc_ops, chip);
	if (IS_ERR_OR_NULL(tcpc))
		return -EINVAL;
	chip->tcpc = tcpc;

#if CONFIG_USB_PD_DISABLE_PE
	tcpc->disable_pe = of_property_read_bool(np, "up-tcpc,disable-pe") ||
				 of_property_read_bool(np, "up-tcpc,disable_pe");
#endif	/* CONFIG_USB_PD_DISABLE_PE */

	tcpc->tcpc_flags = TCPC_FLAGS_VCONN_SAFE5V_ONLY;

#if CONFIG_USB_PD_RETRY_CRC_DISCARD
	tcpc->tcpc_flags |= TCPC_FLAGS_RETRY_CRC_DISCARD;
#endif  /* CONFIG_USB_PD_RETRY_CRC_DISCARD */

#if CONFIG_USB_PD_REV30
	tcpc->tcpc_flags |= TCPC_FLAGS_PD_REV30;

	if (tcpc->tcpc_flags & TCPC_FLAGS_PD_REV30)
		dev_info(dev, "PD_REV30\n");
	else
		dev_info(dev, "PD_REV20\n");
#endif	/* CONFIG_USB_PD_REV30 */
	tcpc->tcpc_flags |= TCPC_FLAGS_ALERT_V10;

	return 0;
}

static struct tcpc_ops upm7610_tcpc_ops = {
	.init = upm7610_tcpc_init,    /* type-c port control init upm7610端口控制器初始化 */					
	.alert_status_clear = upm7610_alert_status_clear,	/* upm7610告警状态清除 */
	.fault_status_clear = upm7610_fault_status_clear,
	.set_alert_mask = upm7610_set_alert_mask,	/* upm7610设置告警掩码 */
	.get_alert_mask = upm7610_get_alert_mask,
	.get_alert_status_and_mask = upm7610_get_alert_status_and_mask,	/* upm7610获取告警状态和掩码 */
	.get_power_status = upm7610_get_power_status,	/* upm7610获取电源状态 */
	.get_fault_status = upm7610_get_fault_status,	/* upm7610获取错误状态 */
	.get_cc = upm7610_get_cc,	/* upm7610获取cc管脚状态 */
	.set_cc = upm7610_set_cc,	/* upm7610设置cc管脚状态 */
	.set_polarity = upm7610_set_polarity,	/* upm7610设置极性 */
	.set_vconn = upm7610_set_vconn,
	.deinit = upm7610_tcpc_deinit,

	.set_low_power_mode = upm7610_set_low_power_mode,	/* upm7610 设置低功耗电源模式 */

#if IS_ENABLED(CONFIG_USB_POWER_DELIVERY)
	.set_msg_header = upm7610_set_msg_header,			/* upm7610 设置消息头 */
	.set_rx_enable = upm7610_set_rx_enable,	 /* upm7610 接收消息使能 */
	.protocol_reset = NULL,
	.get_message = upm7610_get_message,		 /* upm7610 获取消息 */
	.transmit = upm7610_transmit,			/* upm7610 传输消息 */
	.set_bist_test_mode = upm7610_set_bist_test_mode,	/* upm7610 自测模式 */
	.set_bist_carrier_mode = upm7610_set_bist_carrier_mode,	/* upm7610 获取负载函数 */
#endif	/* CONFIG_USB_POWER_DELIVERY */

#if CONFIG_USB_PD_RETRY_CRC_DISCARD
	.retransmit = upm7610_retransmit,	/* 信息发送失败时重传函数 */
#endif	/* CONFIG_USB_PD_RETRY_CRC_DISCARD */
};

4.解析upm7610_init_alert函数

定义一个中断线程,接受到中断后中断程序upm7610_intr_handler使用tcpci_alert(tcpci_alert.c)函数将信息同步到框架层。

static int upm7610_init_alert(struct tcpc_device *tcpc)
{
	struct upm7610_chip *chip = tcpc_get_dev_data(tcpc);
	int ret = 0;
	char *name = NULL;

	/* Clear Alert Mask & Status */
	upm7610_write_word(chip->client, TCPC_V10_REG_ALERT_MASK, 0);
	upm7610_write_word(chip->client, TCPC_V10_REG_ALERT, 0xffff);

	name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-IRQ",
			      chip->tcpc_desc->name);
	if (!name)
		return -ENOMEM;

	dev_info(chip->dev, "%s name = %s, gpio = %d\n",
			    __func__, chip->tcpc_desc->name, chip->irq_gpio);

	ret = devm_gpio_request(chip->dev, chip->irq_gpio, name);
	if (ret < 0) {
		dev_notice(chip->dev, "%s request GPIO fail(%d)\n",
				      __func__, ret);
		return ret;
	}

	ret = gpio_direction_input(chip->irq_gpio);
	if (ret < 0) {
		dev_notice(chip->dev, "%s set GPIO fail(%d)\n", __func__, ret);
		return ret;
	}

	ret = gpio_to_irq(chip->irq_gpio);
	if (ret < 0) {
		dev_notice(chip->dev, "%s gpio to irq fail(%d)",
				      __func__, ret);
		return ret;
	}
	chip->irq = ret;

	dev_info(chip->dev, "%s IRQ number = %d\n", __func__, chip->irq);

	device_init_wakeup(chip->dev, true);
	ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL,
					upm7610_intr_handler,
					IRQF_TRIGGER_LOW | IRQF_ONESHOT,	/* 低电平触发中断 中断处理完成后才重新使能中断 */
					name, chip);
	if (ret < 0) {
		dev_notice(chip->dev, "%s request irq fail(%d)\n",
				      __func__, ret);
		return ret;
	}
	enable_irq_wake(chip->irq);

	return 0;
}

static irqreturn_t upm7610_intr_handler(int irq, void *data)
{
	struct upm7610_chip *chip = data;

	mutex_lock(&chip->irq_lock);
	if (chip->is_suspended) {
		dev_notice(chip->dev, "%s irq while suspended\n", __func__);
		chip->irq_while_suspended = true;
		disable_irq_nosync(chip->irq);
		mutex_unlock(&chip->irq_lock);
		return IRQ_NONE;
	}
	mutex_unlock(&chip->irq_lock);

	pm_stay_awake(chip->dev);	/*通知电源管理子系统,防止系统进入 suspend 状态*/
	tcpci_lock_typec(chip->tcpc);
	tcpci_alert(chip->tcpc, false);	/* 告警:处理 Type-C 相关的中断事件(如连接检测、PD 通信等)*/
	tcpci_unlock_typec(chip->tcpc);
	pm_relax(chip->dev);

	return IRQ_HANDLED;
}

5总结

****所以pd驱动中基于pd框架代码主要通过tcpci_alert和tcpc_device_register与框架层交互,去理解Type-C port control interface框架层

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐