Linux驱动开发 - iio子系统(3) 通过spmi读取PMIC的ADC

本文详细介绍了MT635x平台上的IIO设备驱动,重点讨论了设备树配置、spmi通信以及如何通过auxadc_parse_dt函数解析设备树以获取ADC数据。讲解了auxadc_probe函数中的关键步骤,包括驱动注册和ADC值的读取方法。
摘要由CSDN通过智能技术生成

文章目录

IIO设备驱动源码路径

设备树

设备驱动

auxadc_parse_dt

通过spmi读取ADC


IIO设备驱动源码路径

drivers/iio/adc/mt635x-auxadc.c  ,每个设备的路径都不一样,这里以mt635x-auxadc为例做介绍。pmic是MT6330芯片,使用spmi通信。

设备树

&spmi_bus {
	pmic: mt6330 {
		compatible = "mediatek,mt6330";
		reg = <0x4 SPMI_USID 0x1 SPMI_GSID>;
		interrupts = <4>;
		#address-cells = <1>;
		#size-cells = <0>;
		interrupt-controller;
		#interrupt-cells = <2>;

		mt-pmic {
			compatible = "mediatek,mt63xx-debug";
		};

		pmic_auxadc: pmic_auxadc {
			compatible = "mediatek,pmic-auxadc",
				     "mediatek,mt6330-auxadc";
			#io-channel-cells = <1>;
			batadc {
				channel = <AUXADC_BATADC>;
				resistance-ratio = <3 1>;
				avg-num = <128>;
			};
			chip_temp {
				channel = <AUXADC_CHIP_TEMP>;
			};
			vcore_temp {
				channel = <AUXADC_VCORE_TEMP>;
			};
			vproc_temp {
				channel = <AUXADC_VPROC_TEMP>;
			};
			vgpu_temp {
				channel = <AUXADC_VGPU_TEMP>;
			};
			tsx_temp {
				channel = <AUXADC_TSX_TEMP>;
				avg-num = <128>;
			};
			dcxo_temp {
				channel = <AUXADC_DCXO_TEMP>;
				avg-num = <16>;
			};
			dcxo_ext0 {
				channel = <AUXADC_TYPEL>;
				avg-num = <128>;
			};
			dcxo_ext1 {
				channel = <AUXADC_DRDI>;
				avg-num = <128>;
			};
		};
    ...... 后面的类容省略
    };

设备驱动

源码路径 drivers/iio/adc/mt635x-auxadc.c 


static const struct of_device_id mt635x_auxadc_of_match[] = {
	{
		.compatible = "mediatek,mt6330-auxadc",
		.data = &mt6330_info,
	}
};
MODULE_DEVICE_TABLE(of, mt635x_auxadc_of_match);

static struct platform_driver mt635x_auxadc_driver = {
	.driver = {
		.name = "mt635x-auxadc",
		.of_match_table = mt635x_auxadc_of_match,
	},
	.probe	= mt635x_auxadc_probe,
};
module_platform_driver(mt635x_auxadc_driver);

驱动代码里面的compatible的值为"mediatek,mt6330-auxadc",与设备树中的compatible值相等,compatible匹配后执行mt635x_auxadc_probe函数。 

static int mt635x_auxadc_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct mt635x_auxadc_device *adc_dev;
	struct iio_dev *indio_dev;
	struct mt6397_chip *chip;
	int ret, imp_irq;

	chip = dev_get_drvdata(pdev->dev.parent);
	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev));
	if (!indio_dev)
		return -ENOMEM;

	adc_dev = iio_priv(indio_dev);
	adc_dev->regmap = chip->regmap;
	adc_dev->dev = &pdev->dev;
	mutex_init(&adc_dev->lock);
	init_completion(&adc_dev->imp_done);
	device_init_wakeup(&pdev->dev, true);
	adc_dev->info = of_device_get_match_data(&pdev->dev);
	if (adc_dev->info->imp_conv) {
		imp_irq = platform_get_irq_byname(pdev, "imp");
		if (imp_irq < 0) {
			dev_notice(&pdev->dev, "failed to get IMP irq, ret=%d\n",
				   imp_irq);
			return imp_irq;
		}
		ret = devm_request_threaded_irq(&pdev->dev, imp_irq, NULL, imp_isr,
						IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
						"auxadc_imp", adc_dev);
		if (ret) {
			dev_notice(&pdev->dev,
				   "failed to request IMP irq, ret=%d\n", ret);
			return ret;
		}
	}

	ret = auxadc_parse_dt(adc_dev, node);
	if (ret < 0) {
		dev_notice(&pdev->dev, "auxadc_parse_dt fail, ret=%d\n", ret);
		return ret;
	}
	auxadc_reset(adc_dev);

	indio_dev->dev.parent = &pdev->dev;
	indio_dev->name = dev_name(&pdev->dev);
	indio_dev->info = &mt635x_auxadc_info;
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->channels = adc_dev->iio_chans;
	indio_dev->num_channels = adc_dev->nchannels;

	ret = devm_iio_device_register(&pdev->dev, indio_dev);
	if (ret < 0) {
		dev_notice(&pdev->dev, "failed to register iio device!\n");
		return ret;
	}
	dev_info(&pdev->dev, "%s done\n", __func__);

	return 0;
}

第9行, chip = dev_get_drvdata(pdev->dev.parent),获取父节点的数据;

第15行, adc_dev->regmap = chip->regmap,获取父节点的regmap,regmap在另外一个文件中初始化,即drivers/mfd/mt6330-core.c的mt6330_probe()函数中,如下:

	regmap = devm_regmap_init(&sdev->dev, &mt6330_regmap_bus,
				pmic, &spmi_regmap_config);
	if (IS_ERR(regmap))
		return PTR_ERR(regmap);
	pmic->regmap = regmap;

mt6330_probe()函数暂时不深入了解,继续回到mt635x_auxadc_probe()函数。

第10行, devm_iio_device_alloc为indio_dev和adc_dev申请空间,adc_dev的地址在indio_dev的后面;

第14行, 通过函数iio_priv获取adc_dev的地址; 

第14~19行, 初始化互斥锁、完成量、休眠唤醒功能;

第20~36行, 因为imp_conv没有定义,这里不涉及;

第38行, ret = auxadc_parse_dt(adc_dev, node)设备树解析,下面再分析;

第43行, auxadc_reset(adc_dev),通过spmi通信,复位pmic的ADC;

第45~50行, indio_dev赋值;

第52行, 调用iio_device_register函数,注册到IIO驱动框架里面去,具体怎么实现可以看Linux驱动开发 - iio子系统(1)

auxadc_parse_dt

mt635x_auxadc_probe函数中第49行indio_dev->channels = adc_dev->iio_chans,其中adc_dev->iio_chans就是在auxadc_parse_dt中解析的,


static int auxadc_parse_dt(struct mt635x_auxadc_device *adc_dev,
			   struct device_node *node)
{
	struct iio_chan_spec *iio_chan;
	struct device_node *child;
	unsigned int channel = 0, index = 0;
	int ret;

	adc_dev->nchannels = of_get_available_child_count(node);
	if (!adc_dev->nchannels)
		return -EINVAL;

	adc_dev->iio_chans = devm_kcalloc(adc_dev->dev, adc_dev->nchannels,
		sizeof(*adc_dev->iio_chans), GFP_KERNEL);
	if (!adc_dev->iio_chans)
		return -ENOMEM;
	iio_chan = adc_dev->iio_chans;

	for_each_available_child_of_node(node, child) {
		ret = auxadc_get_data_from_dt(adc_dev, &channel, child);
		if (ret) {
			of_node_put(child);
			return ret;
		}
		if (auxadc_chans[channel].has_regs) {
			auxadc_chans[channel].regs =
				&adc_dev->info->regs_tbl[channel];
		}

		iio_chan->channel = channel;
		iio_chan->datasheet_name = auxadc_chans[channel].ch_name;
		iio_chan->extend_name = auxadc_chans[channel].ch_name;
		iio_chan->info_mask_separate = auxadc_chans[channel].info_mask;
		iio_chan->type = auxadc_chans[channel].type;
		iio_chan->indexed = 1;
		iio_chan->address = index++;

		iio_chan++;
	}

	return 0;
}

第10行, adc_dev->nchannels = of_get_available_child_count(node),获取设备树的子节点数量,可以看到有9个子节点,即对应9个channel; 

第14行, 给iio_chans申请空间;上一篇是定义的全局变量,Linux驱动开发 - iio子系统(2) ADC设备驱动

第20~40行, 给iio_chans赋值和给auxadc_chans[channel].regs赋值;

通过spmi读取ADC

mt635x_auxadc_probe函数中第X行indio_dev->channels = adc_dev->iio_chans,其中adc_dev->iio_chans就是在auxadc_parse_dt中解析的,


static int mt635x_auxadc_read_raw(struct iio_dev *indio_dev,
				  struct iio_chan_spec const *chan,
				  int *val,
				  int *val2,
				  long mask)
{
	struct mt635x_auxadc_device *adc_dev = iio_priv(indio_dev);
	const struct auxadc_channels *auxadc_chan;
	int auxadc_out = 0;
	int ret;

	mutex_lock(&adc_dev->lock);
	pm_stay_awake(adc_dev->dev);

	auxadc_chan = &auxadc_chans[chan->channel];
	switch (chan->channel) {
	case AUXADC_IMP:
		if (adc_dev->info->imp_conv)
			ret = adc_dev->info->imp_conv(adc_dev,
						      &auxadc_out, val2);
		else
			ret = -EINVAL;
		break;
	case AUXADC_IMIX_R:
		auxadc_out = adc_dev->imix_r;
		ret = 0;
		break;
	default:
		if (auxadc_chan->regs)
			ret = get_auxadc_out(adc_dev, auxadc_chan,
					     &auxadc_out);
		else
			ret = -EINVAL;
		break;
	}

	pm_relax(adc_dev->dev);
	mutex_unlock(&adc_dev->lock);
	if (ret != -ETIMEDOUT && ret < 0)
		goto err;

	switch (mask) {
	case IIO_CHAN_INFO_PROCESSED:
		*val = auxadc_out * auxadc_chan->r_ratio[0] * VOLT_FULL;
		*val = (*val / auxadc_chan->r_ratio[1]) >> auxadc_chan->res;
		ret = IIO_VAL_INT;
		break;
	case IIO_CHAN_INFO_RAW:
		*val = auxadc_out;
		ret = IIO_VAL_INT;
		break;
	default:
		return -EINVAL;
	}
	if (chan->channel == AUXADC_IMP)
		ret = IIO_VAL_INT_MULTIPLE;
err:
	return ret;
}

static int mt635x_auxadc_of_xlate(struct iio_dev *indio_dev,
				  const struct of_phandle_args *iiospec)
{
	int i;

	for (i = 0; i < indio_dev->num_channels; i++) {
		if (indio_dev->channels[i].channel == iiospec->args[0])
			return i;
	}

	return -EINVAL;
}

static const struct iio_info mt635x_auxadc_info = {
	.read_raw = &mt635x_auxadc_read_raw,
	.of_xlate = &mt635x_auxadc_of_xlate,
};

第76行,设置read_raw回调函数,当终端执行cat in_voltage0时,将调用read_raw(),即调用mt6577_auxadc_read_raw()函数

第17~36行,不同的通道,获取ADC的方法不一样,常用的是通过default中的get_auxadc_out函数获取ADC。

第31行,get_auxadc_out函数通过spmic通信获取ADC的值,源码如下,regmap_write和regmap_read涉及Regmap子系统,这里不做详细介绍;

第43~55行,ADC的转换,我们使用的是IIO_CHAN_INFO_RAW,所以不用转换,至此,终端执行cat in_voltage0就会返回adc的电压值。

static int get_auxadc_out(struct mt635x_auxadc_device *adc_dev,
			  const struct auxadc_channels *auxadc_chan, int *val)
{
	int ret;

	regmap_write(adc_dev->regmap,
		     auxadc_chan->regs->rqst_reg,
		     BIT(auxadc_chan->regs->rqst_shift));
	usleep_range(auxadc_chan->avg_num * AUXADC_AVG_TIME_US,
		     (auxadc_chan->avg_num + 1) * AUXADC_AVG_TIME_US);

	if (adc_dev->info->attr & AUXADC_ATTR_BURST16)
		ret = regmap_bulk_read_poll_timeout(adc_dev->regmap,
				       auxadc_chan->regs->out_reg,
				       *val,
				       (*val & AUXADC_RDY_BIT),
				       AUXADC_POLL_DELAY_US,
				       AUXADC_TIMEOUT_US);
	else
		ret = regmap_read_poll_timeout(adc_dev->regmap,
						   auxadc_chan->regs->out_reg,
						   *val,
						   (*val & AUXADC_RDY_BIT),
						   AUXADC_POLL_DELAY_US,
						   AUXADC_TIMEOUT_US);

	*val &= BIT(auxadc_chan->res) - 1;
	if (ret == -ETIMEDOUT)
		dev_err(adc_dev->dev, "(%d)Time out!\n", auxadc_chan->ch_num);

	return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值