Linux字符设备驱动 -- regulator子系统

2024-09-01 16:28

本文主要是介绍Linux字符设备驱动 -- regulator子系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 环境
  • regulator子系统简介:
  • Regulator设备的注册
  • Consumer设备的注册

环境

linux 4.9
armv8-A

regulator子系统简介:

关于regulator子系统,可以看下这这些博客:

  • Linux驱动之Regulator子系统
  • Linux 内核之电源篇(加载流程)

regulator,翻译就是调节器。一些可以输出电流电压的设备可以使用该子系统。举个例子,一个PMIC有多路输出,每一路输出都可以给很多外设、芯片供电。那么每一路输出,我都可以认为他是一个regulator。

regulator子系统还是有一个思想就是consumer(消费者),继续刚才那个例子,PMIC的某一路输出,要给DDR供电,那么这个DDR设备就是这一路regulator的consumer,可以注册为一个consumer设备。

当然,这是真正有物理设备的consumer。有的时候为了控制regulator,不需要通过一个真正的物理设备注册的consumer,可以凭空捏造一个consumer,就是virtual consumer。后面分析的时候可以看到,对consumer的开关/电压/电流操作实际上会转化为对regulator的操作。

那为啥不直接控制regulator呢?我也不知道,看上去像是regulator子系统没有提供直接对regulator控制的API,还是要以来consumer提供的API来控制regulator,所以还是要搞个虚拟consumer。不过这一段我的理解可能有问题。

回顾笔记,分析都在注释中。

Regulator设备的注册

对于一个多路输出的PMIC,每一路都可以注册为一个regulator。把每一路注册为一个regulator使用的子系统接口API为:

rdev = devm_regulator_register(&pdev->dev, regulator, &regulator_config);

需要传入参数:

  1. struct device *dev,这里传入&pdev->dev,所有regulator总要有一个父设备的,在设备树中我们把所有regulator节点注册为一个regulators节点的子节点。然后这个regulators节点就是这里的platform_device,但是不是在系统启动时注册的,是我们上一节通过MFD子系统注册的。
  2. const struct regulator_desc *regulator_desc,这是regulator的静态描述信息。这些信息是用来描述regulator设备本身的
  3. const struct regulator_config *config,这是regulator的动态描述信息。这些信息是用来填充一些结构关系的。

第二个参数结构如下:

struct regulator_desc {const char *name;								// g, 该regulator名称const char *supply_name;						// g, const char *of_match;							// g, 用于与设备树中的regulator匹配,会在初测过程中与regulaotr的设备树节点名字匹配,也就是devcice_node->nameconst char *regulators_node;					// g, 一般会在设备树中把所有regulator的节点都挂在一个regulators节点下,此域表示regulators节点的名字。在注册regulator的时候需要用到int (*of_parse_cb)(struct device_node *,		// g, 自己提供,如果of_match存在且匹配,可以用这个函数来解析一些设备树中regulator节点中自己定义的属性,然后做一些自己想做的操作const struct regulator_desc *,struct regulator_config *);int id;											// g, regulator idunsigned int continuous_voltage_range:1;unsigned n_voltages;							// g, 步长数,可控制的电压范围内总共有多少步const struct regulator_ops *ops;				// g, 操作集,该regulator专有的操作集,后面对consumer的控制实际上会调用到这里int irq;enum regulator_type type;struct module *owner;unsigned int min_uV;							// g, 最小电压,控制regulator时不能小与该电压unsigned int uV_step;							// g, 步长,看过PMIC的芯片手册就知道,某一路的输出电压是按步长来的,bit +1 则输出电压+step.但是对于变步长的regulator来说不需要这个unsigned int linear_min_sel;int fixed_uV;unsigned int ramp_delay;int min_dropout_uV;// g, 有些regulator的电压/电流控制是变步长的,就比如说在[a,b)这一段,vsel_reg一个bit的步长为step1,在[b,c]这一段1个bit步长为step2// g, 此时就需要下面这个linear_ranges域,这个域是个数组,每个数组可以表示"[a,b)这一段电压范围使用步长step1, 步的范围为[c,d]"中的a, c, d, step1// g, 则b可由公式 b = a + step1 * (d -c)计算得出const struct regulator_linear_range *linear_ranges;	int n_linear_ranges;const unsigned int *volt_table;unsigned int vsel_reg;							// g, 电压控制寄存器,控制该路regulator输出电压的寄存器unsigned int vsel_mask;							// g, 可能这个电压控制寄存器的某几位控制输出电压,使用该掩码控制这几位特定的bitunsigned int csel_reg;							// g, 电流控制unsigned int csel_mask;							// g, 同上unsigned int apply_reg;unsigned int apply_bit;unsigned int enable_reg;						// g, regulator使能寄存器地址unsigned int enable_mask;						// g, 同上unsigned int enable_val;						unsigned int disable_val;bool enable_is_inverted;unsigned int bypass_reg;unsigned int bypass_mask;unsigned int bypass_val_on;unsigned int bypass_val_off;unsigned int active_discharge_on;unsigned int active_discharge_off;unsigned int active_discharge_mask;unsigned int active_discharge_reg;unsigned int enable_time;unsigned int off_on_delay;unsigned int (*of_map_mode)(unsigned int mode);
};

第三个参数结构如下:

struct regulator_config {struct device *dev;								// g, 一般你的regulators节点挂在哪个设备下,这里就要使用哪个设备。const struct regulator_init_data *init_data;	// g, 一些初始化数据,如果创建实例的时候初始化了这个域,并且无法从设备树中解析到对应的设备,就会使用我们初始化的域。否则,会使用设备树的信息填充该域,就算你初始化了,也会被覆盖。void *driver_data;								// g, 私有数据struct device_node *of_node;					// g, 设备节点struct regmap *regmap;							// g, 使用的regmapbool ena_gpio_initialized;int ena_gpio;unsigned int ena_gpio_invert:1;unsigned int ena_gpio_flags;
};

其中比较重要的是第二个参数,描述了该regulator本身的一些物理特性,具体配置成什么样要根据你使用的regulator来设置。举个例子:


static const struct regulator_desc pmic_regulators_desc [] = {[0] = {...},[1] = {.name = "dcdcb",								// g, 这一路regulator的名字是"dcdcb".supply_name	= 	"vinb",						.of_match	= 		of_match_ptr("dcdcb"),		// g, .of_match="dcdcb",如果有需要的话,可以使用这个域来与设备树节点匹配,当然需要设备树对应的节点名字为"dcdcb".regulators_node = 	of_match_ptr("regulators"),// g, 这个regulator挂在regulators节点下,而且这个regulators节点在设备树中就叫"regulators".type		= 		REGULATOR_VOLTAGE,.id		= 			1,.n_voltages	= 		(2550 - 1000) / 50 + 1, // g, 根据dataset,范围为1v~2.55v.owner		= 		THIS_MODULE,.min_uV		= 		1000 * 1000,			// g,最小电压为1000mv,也就是1v.uV_step	= 		50 * 1000(fix),			// g, 步长为50mv, 这是个定步长regulator.vsel_reg	= 		0x13,					// g, 控制该regulator电压的寄存器地址为0x13.vsel_mask	= 		0x1f,					// g, 寄存器0x13的bit 0~4 控制 DCDC-B的输出voltage.enable_reg	= 		0x10,					// g, 开/关寄存器地址.enable_mask	= 	BIT(1), 				// g, 寄存器0x10的bit 1控制DCDC-B的on-off.ops		= 		&自定义,				// g,自定义,但是很重要。}
};
...
...
static int regulator_probe(struct platform_device *pdev)
{struct regulator_config pmic_regulator_config= {.dev = pdev->dev.parent,		// g, pmu节点,因为在我的设备树下regulators节点是挂在pmu节点下面的.regmap = 之前注册的regmap,.driver_data = 看你自己需要什么,};......struct regulator_dev *rdev = devm_regulator_register(&pdev->dev, pmic_regulators_desc, &pmic_regulator_config);...
}

注册过程是什么情况呢?具体得看devm_regulator_register()的实现过程:

drivers/regulator/devres.c:
struct regulator_dev *devm_regulator_register(struct device *dev,const struct regulator_desc *regulator_desc,const struct regulator_config *config)
{struct regulator_dev **ptr, *rdev;ptr = devres_alloc(devm_rdev_release, sizeof(*ptr),GFP_KERNEL);if (!ptr)return ERR_PTR(-ENOMEM);// g, 这个函数有说法的// g, inrdev = regulator_register(regulator_desc, config);if (!IS_ERR(rdev)) {*ptr = rdev;// g, 会为dev->devres_head添加dr->node->entry// g, 也就是说新注册的regulator_dev是传入的dev的一个devresdevres_add(dev, ptr);		} else {devres_free(ptr);}return rdev;
}

因为是devm_开头的函数,所以肯定是把资源挂在dev->devres_head下,关于devm_系列的函数,后续单独写一篇笔记分析。除此之外,这个函数就单纯的调用了regulator_register(),会注册一个regulator_dev,并返回:

struct regulator_dev *
regulator_register(const struct regulator_desc *regulator_desc,const struct regulator_config *cfg)
{const struct regulation_constraints *constraints = NULL;const struct regulator_init_data *init_data;struct regulator_config *config = NULL;static atomic_t regulator_no = ATOMIC_INIT(-1);struct regulator_dev *rdev;struct device *dev;int ret, i;if (regulator_desc == NULL || cfg == NULL)return ERR_PTR(-EINVAL);dev = cfg->dev;WARN_ON(!dev);if (regulator_desc->name == NULL || regulator_desc->ops == NULL)return ERR_PTR(-EINVAL);if (regulator_desc->type != REGULATOR_VOLTAGE &&regulator_desc->type != REGULATOR_CURRENT)return ERR_PTR(-EINVAL);/* Only one of each should be implemented */WARN_ON(regulator_desc->ops->get_voltage &&regulator_desc->ops->get_voltage_sel);WARN_ON(regulator_desc->ops->set_voltage &&regulator_desc->ops->set_voltage_sel);/* If we're using selectors we must implement list_voltage. */if (regulator_desc->ops->get_voltage_sel &&!regulator_desc->ops->list_voltage) {return ERR_PTR(-EINVAL);}if (regulator_desc->ops->set_voltage_sel &&!regulator_desc->ops->list_voltage) {return ERR_PTR(-EINVAL);}rdev = kzalloc(sizeof(struct regulator_dev), GFP_KERNEL);if (rdev == NULL)return ERR_PTR(-ENOMEM);/** Duplicate the config so the driver could override it after* parsing init data.*/config = kmemdup(cfg, sizeof(*cfg), GFP_KERNEL);if (config == NULL) {kfree(rdev);return ERR_PTR(-ENOMEM);}// g, 这个init_data挺关键的,一般会放在config中,但是我们的config没有定义init_data// g, 此处传入的dev为cfg->dev,也就是config->dev,也就是pdev("regulators")->dev.parent("pmu")// g, 该函数会解析(pmu-->regulators-->子regulator)中的很多属性,赋值给init_data。同时会把子regulator结点作为regulator_dev->dev.of_node// g, ininit_data = regulator_of_get_init_data(dev, regulator_desc, config,&rdev->dev.of_node);if (!init_data) {	// g, 如果解析设备树失败,才会使用config->init_data,所以设备树优先级更高init_data = config->init_data;rdev->dev.of_node = of_node_get(config->of_node);}mutex_init(&rdev->mutex);rdev->reg_data = config->driver_data;rdev->owner = regulator_desc->owner;rdev->desc = regulator_desc;		// g,把desc表放到rdev->descif (config->regmap)rdev->regmap = config->regmap;else if (dev_get_regmap(dev, NULL))rdev->regmap = dev_get_regmap(dev, NULL);else if (dev->parent)rdev->regmap = dev_get_regmap(dev->parent, NULL);INIT_LIST_HEAD(&rdev->consumer_list);INIT_LIST_HEAD(&rdev->list);
#if defined(CONFIG_AW_AXP)INIT_LIST_HEAD(&rdev->axp_enable_list);list_add(&rdev->list, &axp_regulator_list);
#endifBLOCKING_INIT_NOTIFIER_HEAD(&rdev->notifier);INIT_DELAYED_WORK(&rdev->disable_work, regulator_disable_work);/* preform any regulator specific init */if (init_data && init_data->regulator_init) {ret = init_data->regulator_init(rdev->reg_data);if (ret < 0)goto clean;}if ((config->ena_gpio || config->ena_gpio_initialized) &&gpio_is_valid(config->ena_gpio)) {mutex_lock(&regulator_list_mutex);ret = regulator_ena_gpio_request(rdev, config);mutex_unlock(&regulator_list_mutex);if (ret != 0) {rdev_err(rdev, "Failed to request enable GPIO%d: %d\n",config->ena_gpio, ret);goto clean;}}/* register with sysfs */rdev->dev.class = &regulator_class;			// g, 把regulator_dev注册在regulator_classrdev->dev.parent = dev;dev_set_name(&rdev->dev, "regulator.%lu",(unsigned long) atomic_inc_return(&regulator_no));/* set regulator constraints */if (init_data)constraints = &init_data->constraints;if (init_data && init_data->supply_regulator)rdev->supply_name = init_data->supply_regulator;else if (regulator_desc->supply_name)	// g, 这里会进行一个supply_name命名,如"vina", "vinb"rdev->supply_name = regulator_desc->supply_name;/** Attempt to resolve the regulator supply, if specified,* but don't return an error if we fail because we will try* to resolve it again later as more regulators are added.*/if (regulator_resolve_supply(rdev))rdev_dbg(rdev, "unable to resolve supply\n");ret = set_machine_constraints(rdev, constraints);if (ret < 0)goto wash;/* add consumers devices */if (init_data) {mutex_lock(&regulator_list_mutex);for (i = 0; i < init_data->num_consumer_supplies; i++) {ret = set_consumer_device_supply(rdev,				// g, 向全局链表regulator_map_list中添加新的节点,新结点表示新创建的regulator_devinit_data->consumer_supplies[i].dev_name,init_data->consumer_supplies[i].supply);if (ret < 0) {mutex_unlock(&regulator_list_mutex);dev_err(dev, "Failed to set supply %s\n",init_data->consumer_supplies[i].supply);goto unset_supplies;}}mutex_unlock(&regulator_list_mutex);}if (!rdev->desc->ops->get_voltage &&!rdev->desc->ops->list_voltage &&!rdev->desc->fixed_uV)rdev->is_switch = true;dev_set_drvdata(&rdev->dev, rdev);ret = device_register(&rdev->dev);if (ret != 0) {put_device(&rdev->dev);goto unset_supplies;}// g, 使用debugfs_create_dir在debugfs(/sys/kernel/debug/regulator)下创建调试节点// g, 命名为:"rdev->dev.parent->name"-"rdev->name",// g, rdev->dev.parent是传入的cfg->dev,我们设置的cfg->dev就是PMIC的dev// g, 所以最终的debug节点为:/sys/kernel/debug/regulator/PMIC名-aldo1rdev_init_debugfs(rdev);/* try to resolve regulators supply since a new one was registered */class_for_each_device(&regulator_class, NULL, NULL,regulator_register_resolve_supply);kfree(config);return rdev;unset_supplies:mutex_lock(&regulator_list_mutex);unset_regulator_supplies(rdev);mutex_unlock(&regulator_list_mutex);
wash:kfree(rdev->constraints);mutex_lock(&regulator_list_mutex);regulator_ena_gpio_free(rdev);mutex_unlock(&regulator_list_mutex);
clean:kfree(rdev);kfree(config);return ERR_PTR(ret);
}

注册设备,已经相应的调试节点的注册,都在注释中了。其中有个函数需要深入分析一下,这个函数决定了设备树应该怎么写,虽然一般documentation/devicetree/下会告诉你怎么写,但是还是想去看一下。这个函数是regulator_of_get_init_data(),该函数会解析设备树信息填充一个结构体变量struct regulator_init_data init_data,此变量会用来初始化一些regulator_dev的域:

struct regulator_init_data *regulator_of_get_init_data(struct device *dev,const struct regulator_desc *desc,struct regulator_config *config,struct device_node **node)
{struct device_node *search, *child;struct regulator_init_data *init_data = NULL;const char *name;if (!dev->of_node || !desc->of_match)return NULL;// g, desc->regulators_node = "regulators", 自己定义在静态信息表中,可以认为是当前regulator挂在哪个节点下// g, 一般PMIC驱动,每一路输出(regulator)都会挂在一个节点下,该节点可以认为是regulator的集合(在设备树中被命名为"regulators").if (desc->regulators_node)		search = of_get_child_by_name(dev->of_node,	// g, 从dev->of_node遍历寻找"regulators节点",这个节点是PMIC节点,regulator集合节点就挂在PMIC节点下desc->regulators_node);elsesearch = dev->of_node;if (!search) {dev_dbg(dev, "Failed to find regulator container node '%s'\n",desc->regulators_node);return NULL;}// g, 遍历整个"regulators"结点(search),找子结点,在设备树中就是dcdca,dcdcb这些for_each_available_child_of_node(search, child) {name = of_get_property(child, "regulator-compatible", NULL); // g, 该节点无该属性if (!name)name = child->name;	// g, name = child->name,也就是"dcdca", "dcdcb"// g, 如果child->name != of_match,则continue。// g, 806都是相等的,设备树与desc静态信息的fo_match是一致的if (strcmp(desc->of_match, name))	continue;// g, dev是"pmu",child是regulator小结点(pmu-->regulators-->子regulator)// g, 该函数会分配一个struct regulator_init_data, 解析很多child中的属性填充init_data // g, 但是我们的设备树中的regulator中有几个自定义的属性,这里无法解析,需要在外面解析// g, ininit_data = of_get_regulator_init_data(dev, child, desc);if (!init_data) {dev_err(dev,"failed to parse DT for regulator %s\n",child->name);break;}// g, 如果在静态信息表有自定义的of_parse_cb()函数,则需要在parse设备树之后调用回调// g, 比如说,我这个regulaotr下有些单独定义的属性,希望解析这些属性做一些对应的操作,就可以自己初始化一个of_parse_cb()用来解析.if (desc->of_parse_cb) {if (desc->of_parse_cb(child, desc, config)) {dev_err(dev,"driver callback failed to parse DT for regulator %s\n",child->name);init_data = NULL;break;}}of_node_get(child);	// g, 增加引用计数// g, 找到的子regulator结点(pmu-->regulators-->子regulator)会作为rdev(regulator_dev)->dev.of_node*node = child;		break;}of_node_put(search);	// g, 减少引用计数return init_data;
}

这样就为regulator注册好了regulator_dev,接下来需要注册consumer。

Consumer设备的注册

对于设备树中的virtual-consumer设备,可以使用regulator子系统提供的probe函数,匹配方法为:

drivers/regulator/virtual.c:
static struct platform_driver regulator_virtual_consumer_driver = {.probe		= regulator_virtual_probe,.remove		= regulator_virtual_remove,.driver		= {.name		= "reg-virt-consumer",},
};

可以看到,只能通过driver->name与platform_dev->name匹配这种方式进行匹配。不过在MFD设备注册的时候我们已经把virtual_consumer对应的cell所注册而成的plat_form_dev->name设置了"reg-virt-consumer"。

所以最终是使用regulator子系统提供的这个virtual-consumer驱动的。probe过程为regulator_virtual_probe():

drivers/regulator/virtual.c:
static int regulator_virtual_probe(struct platform_device *pdev)
{char *reg_id = dev_get_platdata(&pdev->dev);	// g, 拿到dev->platform_data,也就是AXP806_DCDC1_NAME("dcdc1")这些struct virtual_consumer_data *drvdata;int ret;drvdata = devm_kzalloc(&pdev->dev, sizeof(struct virtual_consumer_data),GFP_KERNEL);if (drvdata == NULL)return -ENOMEM;mutex_init(&drvdata->lock);// g, 该函数的查找方式并不是通过reg_id和regulator_dev中的什么属性进行匹配// g, 而是将"reg_id"拚上"-supply"变成"reg_id-supply",然后调用of_parse_phandle()函数进行搜索device_node// g, 该函数会根据传入的结点,找到该结点中包含的引用属性"reg_id-supply"所指向的结点,// g, 比如说该结点包含一个引用属性"dcdc1-supply",设备树中的完整格式是:dcdc1-supply = <&reg_dcdc1>;// g, 最终找到的device_node就是reg_dcdc1对应的devcie_node, 这是一种隐式phandle(就是被引用的结点)// g, 再使用container_of找到node对应的regulator_dev// g, 下面这个函数,通过consumer_dev和reg_id,找到对应的regulator_dev,并且新建立一个struct regulator,挂在regulaot_dev->consumer_list链表下// g, 找节点的方式比较特殊,需要设备树的配合,也就是说设备树需要按照规范来写// g, indrvdata->regulator = devm_regulator_get(&pdev->dev, reg_id);if (IS_ERR(drvdata->regulator)) {ret = PTR_ERR(drvdata->regulator);dev_err(&pdev->dev, "Failed to obtain supply '%s': %d\n",reg_id, ret);return ret;}// g, 创建调试结点// g, 每个kobject,在sysfs中都会对应一个目录ret = sysfs_create_group(&pdev->dev.kobj,&regulator_virtual_attr_group);if (ret != 0) {dev_err(&pdev->dev,"Failed to create attribute group: %d\n", ret);return ret;}// 这些regulator_xx等等函数,最终都会调用到drvdata->regulator->desc->ops->xxx,也就是为regulator_dev注册的ops函数drvdata->mode = regulator_get_mode(drvdata->regulator);platform_set_drvdata(pdev, drvdata);return 0;
}

最重要的工作就是进行consumer和regulator的匹配工作,也就是说给virtual-consumer找到对应的regulator。此处工作由函数devm_regulator_get()实现:

drivers/regulator/devres.c:
struct regulator *devm_regulator_get(struct device *dev, const char *id)
{return _devm_regulator_get(dev, id, NORMAL_GET);
}--->static struct regulator *_devm_regulator_get(struct device *dev, const char *id,int get_type)
{struct regulator **ptr, *regulator;ptr = devres_alloc(devm_regulator_release, sizeof(*ptr), GFP_KERNEL);if (!ptr)return ERR_PTR(-ENOMEM);// gm 传入的是NORMAL_GETswitch (get_type) {case NORMAL_GET:								// g, 通过NORMAL_GET的方式获取regulator = regulator_get(dev, id);break;case EXCLUSIVE_GET:regulator = regulator_get_exclusive(dev, id);break;case OPTIONAL_GET:regulator = regulator_get_optional(dev, id);break;default:regulator = ERR_PTR(-EINVAL);}if (!IS_ERR(regulator)) {*ptr = regulator;devres_add(dev, ptr);		// g, 把put函数加入到devres中,这样在remove模块的时候就可以自动调用put函数了} else {devres_free(ptr);}return regulator;
}

通过NORMAL_GET方式简历virtual-consumer和regulator_dev的联系,调用函数regulator_get():

drivers/regulator/core.c;
struct regulator *regulator_get(struct device *dev, const char *id)
{return _regulator_get(dev, id, false, true);
}--->static struct regulator *_regulator_get(struct device *dev, const char *id,bool exclusive, bool allow_dummy)	// g, false, true
{struct regulator_dev *rdev;struct regulator *regulator = ERR_PTR(-EPROBE_DEFER);const char *devname = NULL;int ret;if (id == NULL) {pr_err("get() with no identifier\n");return ERR_PTR(-EINVAL);}if (dev)devname = dev_name(dev);if (have_full_constraints())ret = -ENODEV;elseret = -EPROBE_DEFER;rdev = regulator_dev_lookup(dev, id, &ret);	// g, 这个函数会负责找到对应的regulator设备,也就是找到该consumer对应的struct regulator_devif (rdev)goto found;regulator = ERR_PTR(ret);/** If we have return value from dev_lookup fail, we do not expect to* succeed, so, quit with appropriate error value*/if (ret && ret != -ENODEV)return regulator;if (!devname)devname = "deviceless";/** Assume that a regulator is physically present and enabled* even if it isn't hooked up and just provide a dummy.*/if (have_full_constraints() && allow_dummy) {pr_warn("%s supply %s not found, using dummy regulator\n",devname, id);rdev = dummy_regulator_rdev;get_device(&rdev->dev);goto found;/* Don't log an error when called from regulator_get_optional() */} else if (!have_full_constraints() || exclusive) {dev_warn(dev, "dummy supplies not allowed\n");}return regulator;found:if (rdev->exclusive) {				// g, 如果有独占标志regulator = ERR_PTR(-EPERM);put_device(&rdev->dev);return regulator;}if (exclusive && rdev->open_count) {	// g, 如果想要独占regulator,并且已经被打开过了.实际上这里传入的exclusive = false, 所以这个if不起作用regulator = ERR_PTR(-EBUSY);put_device(&rdev->dev);return regulator;}ret = regulator_resolve_supply(rdev);if (ret < 0) {regulator = ERR_PTR(ret);put_device(&rdev->dev);return regulator;}if (!try_module_get(rdev->owner)) {put_device(&rdev->dev);return regulator;}// g, 如果上述检查都通过的话,会为regulator_dev创建一个struct regulator// g, 下面这个函数会将新创建的struct regulator加入到regulator_dev->consumer_list链表中// g, 并且初始化regulator的一些域,比如说使regulator->dev指向传入的consumer_dev// g, 会更新/sys观测目录下的一些观测节点,比如说在regulator_dev目录(/sys/class/regulator/regulator.x)下建立指向consumer_dev(xx/reg-virt-cosumer.3-id)的链接.// g, 会在rdev->debugfs(/sys/kernel/debug/regulator/xxx-aldo1)下建立一个调试文件夹regulator->supply_name(/sys/kernel/debug/regulator/xxx-aldo1/reg-virt-consumer.6-aldo1),里面有些调试结点regulator = create_regulator(rdev, dev, id);if (regulator == NULL) {regulator = ERR_PTR(-ENOMEM);put_device(&rdev->dev);module_put(rdev->owner);return regulator;}rdev->open_count++;if (exclusive) {rdev->exclusive = 1;ret = _regulator_is_enabled(rdev);if (ret > 0)rdev->use_count = 1;elserdev->use_count = 0;}return regulator;
}

在创建struct regulator的过程中,会调用regulator_dev_lookup()函数,该函数根据virual-consumer的信息,来找到对应的struct regulator_dev,也就是前面注册的regulator设备。其实现过程如下:

static struct regulator_dev *regulator_dev_lookup(struct device *dev,const char *supply,int *ret)
{struct regulator_dev *r;struct device_node *node;struct regulator_map *map;const char *devname = NULL;regulator_supply_alias(&dev, &supply);/* first do a dt based lookup */if (dev && dev->of_node) {node = of_get_regulator(dev, supply);		// g, 找到引用节点,也就是consumer节点要使用哪一个regulator节点,会在设备树中以引用节点的形式表达if (node) {r = of_find_regulator_by_node(node);	// g, 上面找到node后,获取对应的device,再通过device找到真正的regulator节点对应的struct regulator_devif (r)return r;*ret = -EPROBE_DEFER;return NULL;} else {/** If we couldn't even get the node then it's* not just that the device didn't register* yet, there's no node and we'll never* succeed.*/*ret = -ENODEV;}}/* if not found, try doing it non-dt way */// g, 如果上面没有找到,则不会return, 则需要换其他途径寻找.if (dev)devname = dev_name(dev);// g, 搜索方案二:从regulator_class下寻找是否有名为"supply"的devicer = regulator_lookup_by_name(supply);if (r)return r;mutex_lock(&regulator_list_mutex);// g, 搜索方案三:从全局变量regulator_map_list中搜索,是否有名字能匹配的regulator_dev// g, 注册regulator_dev时会向regulator_map_list添加节点.list_for_each_entry(map, &regulator_map_list, list) {/* If the mapping has a device set up it must match */if (map->dev_name &&(!devname || strcmp(map->dev_name, devname)))continue;if (strcmp(map->supply, supply) == 0 &&get_device(&map->regulator->dev)) {mutex_unlock(&regulator_list_mutex);return map->regulator;}}mutex_unlock(&regulator_list_mutex);return NULL;
}

使用了三种匹配方案,第一种方案是通过设备树节点信息匹配,通过of_get_regulator()函数来找到对应的regulator对应的节点device_node:

drivers/regulator/core.c:
static struct device_node *of_get_regulator(struct device *dev, const char *supply)
{struct device_node *regnode = NULL;char prop_name[32]; /* 32 is max size of property name */dev_dbg(dev, "Looking up %s-supply from device tree\n", supply);snprintf(prop_name, 32, "%s-supply", supply);			// g, prop_name = dcdc1-supply// g, 该函数查找"dcdc1-supply"这个引用属性来找对应的结点// g, 比如说设备树中的一个virtual-consumer节点中的一个属性:dcdc1-supply = <&reg_dcdc1>;// g, 那么这个函数返回reg_dcdc1对应的节点.regnode = of_parse_phandle(dev->of_node, prop_name, 0);	if (!regnode) {dev_dbg(dev, "Looking up %s property in node %s failed",prop_name, dev->of_node->full_name);return NULL;}return regnode;
}

带of的函数都是与设备树有关的,该函数由regulator子系统实现,其中使用到了of_parse_phandle()函数。这个of函数是我第一次见,它的作用是根据引用节点名来寻找引用节点。所以为了使用第一种匹配方式,设备树中应这样实现:

	regulator0: regulators@0 {reg_dcdc1: dcdca {xxx = <xxx>};...// g, 其他regulator...};virtual-dcdc1 {...dcdc1-supply = <&reg_dcdc1>;			// g, 引用节点...};...// g, 其他虚拟consumer节点...

通过of_parse_phandle(virtual-dcdc1对应的dev_node,“dcdc1-supply”),就可以获取到regulator节点了。这种方法相当于提前在设备树中对virtual-consumer和regulator进行了绑定。

找到匹配的regulator对应的dev_node后,再通过of_find_regulator_by_node()函数找到对应的regulator_dev即可:

static struct regulator_dev *of_find_regulator_by_node(struct device_node *np)
{struct device *dev;dev = class_find_device(&regulator_class, NULL, np, of_node_match);	// g, 在regulator_class下找到node对应的devicereturn dev ? dev_to_rdev(dev) : NULL;			// g, 找到该struct device所属的struct regulator_dev 
}

找到这个virtual-consumer对应的regulator_dev之后,接下来会创建struct regulator,创建struct regulator的函数为create_regulator(),就不展开说了,该函数实现的功能写在注释中了,主要就是把新创建的struct regulator添加到regulator_dev->consumer_list中,表示该struct regulator是该regulator_dev的一个consumer;并且为该virtual-consumer创建一些/sys下的调试节点。

这样,devm_regulator_get()的工作就完成了,再次之后我们调用regulator子系统提供的接口,都需要使用到刚刚创建的struct regulator,因为struct regulator->rdev域保存着匹配到的regulator_dev。

之后,就可以用virtual.c的probe过程中创建的调试节点,通过virtual-consumer来控制真正的regulator设备了。关于这些调试节点,就不说了,就是电流电压控制/开启关闭这些。

这篇关于Linux字符设备驱动 -- regulator子系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127448

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面

LinuxMint怎么安装? Linux Mint22下载安装图文教程

《LinuxMint怎么安装?LinuxMint22下载安装图文教程》LinuxMint22发布以后,有很多新功能,很多朋友想要下载并安装,该怎么操作呢?下面我们就来看看详细安装指南... linux Mint 是一款基于 Ubuntu 的流行发行版,凭借其现代、精致、易于使用的特性,深受小伙伴们所喜爱。对

什么是 Linux Mint? 适合初学者体验的桌面操作系统

《什么是LinuxMint?适合初学者体验的桌面操作系统》今天带你全面了解LinuxMint,包括它的历史、功能、版本以及独特亮点,话不多说,马上开始吧... linux Mint 是一款基于 Ubuntu 和 Debian 的知名发行版,它的用户体验非常友好,深受广大 Linux 爱好者和日常用户的青睐,

Linux(Centos7)安装Mysql/Redis/MinIO方式

《Linux(Centos7)安装Mysql/Redis/MinIO方式》文章总结:介绍了如何安装MySQL和Redis,以及如何配置它们为开机自启,还详细讲解了如何安装MinIO,包括配置Syste... 目录安装mysql安装Redis安装MinIO总结安装Mysql安装Redis搜索Red

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或