2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。

这篇具有很好参考价值的文章主要介绍了2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Linux IIO(Industrial I/O)架构是Linux内核提供的一种用于支持各种类型传感器和数据采集设备的子系统,包括温度、压力、湿度、加速度、光度等多种传感器。

二、这个就是ads1015的驱动,里面用到iio子系统。

ti-ads1015.c « adc « iio « drivers - kernel/git/torvalds/linux.git - Linux kernel source tree

三、rk的adc 也是用iio ,简单来分析adc 按键调用iio 获取adc值的例子。

     3.1 adc 的 iio 驱动 kernel\drivers\iio\adc\rockchip_saradc.c,可以简单理解为提供iio接口给其他的驱动调用。

/*
 * Rockchip Successive Approximation Register (SAR) A/D Converter
 * Copyright (C) 2014 ROCKCHIP, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/reset.h>
#include <linux/regulator/consumer.h>
#include <linux/iio/iio.h>

#define SARADC_DATA			0x00

#define SARADC_STAS			0x04
#define SARADC_STAS_BUSY		BIT(0)

#define SARADC_CTRL			0x08
#define SARADC_CTRL_IRQ_STATUS		BIT(6)
#define SARADC_CTRL_IRQ_ENABLE		BIT(5)
#define SARADC_CTRL_POWER_CTRL		BIT(3)
#define SARADC_CTRL_CHN_MASK		0x7

#define SARADC_DLY_PU_SOC		0x0c
#define SARADC_DLY_PU_SOC_MASK		0x3f

#define SARADC_TIMEOUT			msecs_to_jiffies(100)

struct rockchip_saradc_data {
	int				num_bits;
	const struct iio_chan_spec	*channels;
	int				num_channels;
	unsigned long			clk_rate;
};

struct rockchip_saradc {
	void __iomem		*regs;
	struct clk		*pclk;
	struct clk		*clk;
	struct completion	completion;
	struct regulator	*vref;
	int			uv_vref;
	struct reset_control	*reset;
	const struct rockchip_saradc_data *data;
	u16			last_val;
};

static int rockchip_saradc_read_raw(struct iio_dev *indio_dev,
				    struct iio_chan_spec const *chan,
				    int *val, int *val2, long mask)
{
	struct rockchip_saradc *info = iio_priv(indio_dev);

	switch (mask) {
	case IIO_CHAN_INFO_RAW:
		mutex_lock(&indio_dev->mlock);

		reinit_completion(&info->completion);

		/* 8 clock periods as delay between power up and start cmd */
		writel_relaxed(8, info->regs + SARADC_DLY_PU_SOC);

		/* Select the channel to be used and trigger conversion */
		writel(SARADC_CTRL_POWER_CTRL
				| (chan->channel & SARADC_CTRL_CHN_MASK)
				| SARADC_CTRL_IRQ_ENABLE,
		       info->regs + SARADC_CTRL);

		if (!wait_for_completion_timeout(&info->completion,
						 SARADC_TIMEOUT)) {
			writel_relaxed(0, info->regs + SARADC_CTRL);
			mutex_unlock(&indio_dev->mlock);
			return -ETIMEDOUT;
		}

		*val = info->last_val;
		mutex_unlock(&indio_dev->mlock);
		return IIO_VAL_INT;
	case IIO_CHAN_INFO_SCALE:
		/* It is a dummy regulator */
		if (info->uv_vref < 0)
			return info->uv_vref;

		*val = info->uv_vref / 1000;
		*val2 = info->data->num_bits;
		return IIO_VAL_FRACTIONAL_LOG2;
	default:
		return -EINVAL;
	}
}

static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id)
{
	struct rockchip_saradc *info = (struct rockchip_saradc *)dev_id;

	/* Read value */
	info->last_val = readl_relaxed(info->regs + SARADC_DATA);
	info->last_val &= GENMASK(info->data->num_bits - 1, 0);

	/* Clear irq & power down adc */
	writel_relaxed(0, info->regs + SARADC_CTRL);

	complete(&info->completion);

	return IRQ_HANDLED;
}

static const struct iio_info rockchip_saradc_iio_info = {
	.read_raw = rockchip_saradc_read_raw,
	.driver_module = THIS_MODULE,
};

#define ADC_CHANNEL(_index, _id) {				\
	.type = IIO_VOLTAGE,					\
	.indexed = 1,						\
	.channel = _index,					\
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
	.datasheet_name = _id,					\
}

static const struct iio_chan_spec rockchip_saradc_iio_channels[] = {
	ADC_CHANNEL(0, "adc0"),
	ADC_CHANNEL(1, "adc1"),
	ADC_CHANNEL(2, "adc2"),
};

static const struct rockchip_saradc_data saradc_data = {
	.num_bits = 10,
	.channels = rockchip_saradc_iio_channels,
	.num_channels = ARRAY_SIZE(rockchip_saradc_iio_channels),
	.clk_rate = 1000000,
};

static const struct iio_chan_spec rockchip_rk3066_tsadc_iio_channels[] = {
	ADC_CHANNEL(0, "adc0"),
	ADC_CHANNEL(1, "adc1"),
};

static const struct rockchip_saradc_data rk3066_tsadc_data = {
	.num_bits = 12,
	.channels = rockchip_rk3066_tsadc_iio_channels,
	.num_channels = ARRAY_SIZE(rockchip_rk3066_tsadc_iio_channels),
	.clk_rate = 50000,
};

static const struct iio_chan_spec rockchip_rk3399_saradc_iio_channels[] = {
	ADC_CHANNEL(0, "adc0"),
	ADC_CHANNEL(1, "adc1"),
	ADC_CHANNEL(2, "adc2"),
	ADC_CHANNEL(3, "adc3"),
	ADC_CHANNEL(4, "adc4"),
	ADC_CHANNEL(5, "adc5"),
};

static const struct rockchip_saradc_data rk3399_saradc_data = {
	.num_bits = 10,
	.channels = rockchip_rk3399_saradc_iio_channels,
	.num_channels = ARRAY_SIZE(rockchip_rk3399_saradc_iio_channels),
	.clk_rate = 1000000,
};

static const struct of_device_id rockchip_saradc_match[] = {
	{
		.compatible = "rockchip,saradc",
		.data = &saradc_data,
	}, {
		.compatible = "rockchip,rk3066-tsadc",
		.data = &rk3066_tsadc_data,
	}, {
		.compatible = "rockchip,rk3399-saradc",
		.data = &rk3399_saradc_data,
	},
	{},
};
MODULE_DEVICE_TABLE(of, rockchip_saradc_match);

/**
 * Reset SARADC Controller.
 */
static void rockchip_saradc_reset_controller(struct reset_control *reset)
{
	reset_control_assert(reset);
	usleep_range(10, 20);
	reset_control_deassert(reset);
}

static int rockchip_saradc_probe(struct platform_device *pdev)
{
	struct rockchip_saradc *info = NULL;
	struct device_node *np = pdev->dev.of_node;
	struct iio_dev *indio_dev = NULL;
	struct resource	*mem;
	const struct of_device_id *match;
	int ret;
	int irq;

	if (!np)
		return -ENODEV;

	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
	if (!indio_dev) {
		dev_err(&pdev->dev, "failed allocating iio device\n");
		return -ENOMEM;
	}
	info = iio_priv(indio_dev);

	match = of_match_device(rockchip_saradc_match, &pdev->dev);
	info->data = match->data;

	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	info->regs = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(info->regs))
		return PTR_ERR(info->regs);

	/*
	 * The reset should be an optional property, as it should work
	 * with old devicetrees as well
	 */
	info->reset = devm_reset_control_get(&pdev->dev, "saradc-apb");
	if (IS_ERR(info->reset)) {
		ret = PTR_ERR(info->reset);
		if (ret != -ENOENT)
			return ret;

		dev_dbg(&pdev->dev, "no reset control found\n");
		info->reset = NULL;
	}

	init_completion(&info->completion);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq resource?\n");
		return irq;
	}

	ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr,
			       0, dev_name(&pdev->dev), info);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed requesting irq %d\n", irq);
		return ret;
	}

	info->pclk = devm_clk_get(&pdev->dev, "apb_pclk");
	if (IS_ERR(info->pclk)) {
		dev_err(&pdev->dev, "failed to get pclk\n");
		return PTR_ERR(info->pclk);
	}

	info->clk = devm_clk_get(&pdev->dev, "saradc");
	if (IS_ERR(info->clk)) {
		dev_err(&pdev->dev, "failed to get adc clock\n");
		return PTR_ERR(info->clk);
	}

	info->vref = devm_regulator_get(&pdev->dev, "vref");
	if (IS_ERR(info->vref)) {
		dev_err(&pdev->dev, "failed to get regulator, %ld\n",
			PTR_ERR(info->vref));
		return PTR_ERR(info->vref);
	}

	if (info->reset)
		rockchip_saradc_reset_controller(info->reset);

	/*
	 * Use a default value for the converter clock.
	 * This may become user-configurable in the future.
	 */
	ret = clk_set_rate(info->clk, info->data->clk_rate);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to set adc clk rate, %d\n", ret);
		return ret;
	}

	ret = regulator_enable(info->vref);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to enable vref regulator\n");
		return ret;
	}

	info->uv_vref = regulator_get_voltage(info->vref);

	ret = clk_prepare_enable(info->pclk);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to enable pclk\n");
		goto err_reg_voltage;
	}

	ret = clk_prepare_enable(info->clk);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to enable converter clock\n");
		goto err_pclk;
	}

	platform_set_drvdata(pdev, indio_dev);

	indio_dev->name = dev_name(&pdev->dev);
	indio_dev->dev.parent = &pdev->dev;
	indio_dev->dev.of_node = pdev->dev.of_node;
	indio_dev->info = &rockchip_saradc_iio_info;
	indio_dev->modes = INDIO_DIRECT_MODE;

	indio_dev->channels = info->data->channels;
	indio_dev->num_channels = info->data->num_channels;

	ret = iio_device_register(indio_dev);
	if (ret)
		goto err_clk;

	return 0;

err_clk:
	clk_disable_unprepare(info->clk);
err_pclk:
	clk_disable_unprepare(info->pclk);
err_reg_voltage:
	regulator_disable(info->vref);
	return ret;
}

static int rockchip_saradc_remove(struct platform_device *pdev)
{
	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
	struct rockchip_saradc *info = iio_priv(indio_dev);

	iio_device_unregister(indio_dev);
	clk_disable_unprepare(info->clk);
	clk_disable_unprepare(info->pclk);
	regulator_disable(info->vref);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int rockchip_saradc_suspend(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct rockchip_saradc *info = iio_priv(indio_dev);

	clk_disable_unprepare(info->clk);
	clk_disable_unprepare(info->pclk);
	regulator_disable(info->vref);

	return 0;
}

static int rockchip_saradc_resume(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct rockchip_saradc *info = iio_priv(indio_dev);
	int ret;

	ret = regulator_enable(info->vref);
	if (ret)
		return ret;

	ret = clk_prepare_enable(info->pclk);
	if (ret)
		return ret;

	ret = clk_prepare_enable(info->clk);
	if (ret)
		return ret;

	return ret;
}
#endif

static SIMPLE_DEV_PM_OPS(rockchip_saradc_pm_ops,
			 rockchip_saradc_suspend, rockchip_saradc_resume);

static struct platform_driver rockchip_saradc_driver = {
	.probe		= rockchip_saradc_probe,
	.remove		= rockchip_saradc_remove,
	.driver		= {
		.name	= "rockchip-saradc",
		.of_match_table = rockchip_saradc_match,
		.pm	= &rockchip_saradc_pm_ops,
	},
};

module_platform_driver(rockchip_saradc_driver);

MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
MODULE_DESCRIPTION("Rockchip SARADC driver");
MODULE_LICENSE("GPL v2");

        3.2 kernel\drivers\input\keyboard\rk_keys.c 里面通过iio_channel_get 和iio_read_channel_raw 去读取。最简单的理解其实就是调iio驱动。

/*
 * Driver for keys on GPIO lines capable of generating interrupts.
 *
 * Copyright (C) 2015, Fuzhou Rockchip Electronics Co., Ltd
 * Copyright 2005 Phil Blundell
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/wakelock.h>

#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>

#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/rk_keys.h>

#define EMPTY_DEFAULT_ADVALUE		1024
#define DRIFT_DEFAULT_ADVALUE		70
#define INVALID_ADVALUE			-1
#define EV_ENCALL			KEY_F4
#define EV_MENU				KEY_F1

#if 0
#define key_dbg(bdata, format, arg...)		\
	dev_info(&bdata->input->dev, format, ##arg)
#else
#define key_dbg(bdata, format, arg...)
#endif

#define DEBOUNCE_JIFFIES	(10 / (MSEC_PER_SEC / HZ))	/* 10ms */
#define ADC_SAMPLE_JIFFIES	(100 / (MSEC_PER_SEC / HZ))	/* 100ms */
#define WAKE_LOCK_JIFFIES	(1 * HZ)			/* 1s */

enum rk_key_type {
	TYPE_GPIO = 1,
	TYPE_ADC
};

struct rk_keys_button {
	struct device *dev;
	u32 type;		/* TYPE_GPIO, TYPE_ADC */
	u32 code;		/* key code */
	const char *desc;	/* key label */
	u32 state;		/* key up & down state */
	int gpio;		/* gpio only */
	int adc_value;		/* adc only */
	int adc_state;		/* adc only */
	int active_low;		/* gpio only */
	int wakeup;		/* gpio only */
	struct timer_list timer;
};

struct rk_keys_drvdata {
	int nbuttons;
	/* flag to indicate if we're suspending/resuming */
	bool in_suspend;
	int result;
	int rep;
	int drift_advalue;
	struct wake_lock wake_lock;
	struct input_dev *input;
	struct delayed_work adc_poll_work;
	struct iio_channel *chan;
	struct rk_keys_button button[0];
};

static struct input_dev *sinput_dev;

void rk_send_power_key(int state)
{
	if (!sinput_dev)
		return;
	if (state) {
		input_report_key(sinput_dev, KEY_POWER, 1);
		input_sync(sinput_dev);
	} else {
		input_report_key(sinput_dev, KEY_POWER, 0);
		input_sync(sinput_dev);
	}
}
EXPORT_SYMBOL(rk_send_power_key);

void rk_send_wakeup_key(void)
{
	if (!sinput_dev)
		return;

	input_report_key(sinput_dev, KEY_WAKEUP, 1);
	input_sync(sinput_dev);
	input_report_key(sinput_dev, KEY_WAKEUP, 0);
	input_sync(sinput_dev);
}
EXPORT_SYMBOL(rk_send_wakeup_key);

static void keys_timer(unsigned long _data)
{
	struct rk_keys_button *button = (struct rk_keys_button *)_data;
	struct rk_keys_drvdata *pdata = dev_get_drvdata(button->dev);
	struct input_dev *input = pdata->input;
	int state;

	if (button->type == TYPE_GPIO)
		state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^
			   button->active_low);
	else
		state = !!button->adc_state;

	if (button->state != state) {
		button->state = state;
		input_event(input, EV_KEY, button->code, button->state);
		key_dbg(pdata, "%skey[%s]: report event[%d] state[%d]\n",
			button->type == TYPE_ADC ? "adc" : "gpio",
			button->desc, button->code, button->state);
		input_event(input, EV_KEY, button->code, button->state);
		input_sync(input);
	}

	if (state)
		mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);
}

static irqreturn_t keys_isr(int irq, void *dev_id)
{
	struct rk_keys_button *button = (struct rk_keys_button *)dev_id;
	struct rk_keys_drvdata *pdata = dev_get_drvdata(button->dev);
	struct input_dev *input = pdata->input;

	BUG_ON(irq != gpio_to_irq(button->gpio));

	if (button->wakeup && pdata->in_suspend) {
		button->state = 1;
		key_dbg(pdata,
			"wakeup: %skey[%s]: report event[%d] state[%d]\n",
			(button->type == TYPE_ADC) ? "adc" : "gpio",
			button->desc, button->code, button->state);
		input_event(input, EV_KEY, button->code, button->state);
		input_sync(input);
	}
	if (button->wakeup)
		wake_lock_timeout(&pdata->wake_lock, WAKE_LOCK_JIFFIES);
	mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);

	return IRQ_HANDLED;
}

/*
static ssize_t adc_value_show(struct device *dev, struct device_attribute *attr,
			      char *buf)
{
	struct rk_keys_drvdata *ddata = dev_get_drvdata(dev);

	return sprintf(buf, "adc_value: %d\n", ddata->result);
}
static DEVICE_ATTR(get_adc_value, S_IRUGO | S_IWUSR, adc_value_show, NULL);
*/

static const struct of_device_id rk_key_match[] = {
	{ .compatible = "rockchip,key", .data = NULL},
	{},
};
MODULE_DEVICE_TABLE(of, rk_key_match);

static int rk_key_adc_iio_read(struct rk_keys_drvdata *data)
{
	struct iio_channel *channel = data->chan;
	int val, ret;

	if (!channel)
		return INVALID_ADVALUE;
	ret = iio_read_channel_raw(channel, &val);
	if (ret < 0) {
		pr_err("read channel() error: %d\n", ret);
		return ret;
	}
	return val;
}

static void adc_key_poll(struct work_struct *work)
{
	struct rk_keys_drvdata *ddata;
	int i, result = -1;

	ddata = container_of(work, struct rk_keys_drvdata, adc_poll_work.work);
	if (!ddata->in_suspend) {
		result = rk_key_adc_iio_read(ddata);
		if (result > INVALID_ADVALUE &&
		    result < (EMPTY_DEFAULT_ADVALUE - ddata->drift_advalue))
			ddata->result = result;
		for (i = 0; i < ddata->nbuttons; i++) {
			struct rk_keys_button *button = &ddata->button[i];

			if (!button->adc_value)
				continue;
			if (result < button->adc_value + ddata->drift_advalue &&
			    result > button->adc_value - ddata->drift_advalue)
				button->adc_state = 1;
			else
				button->adc_state = 0;
			if (button->state != button->adc_state)
				mod_timer(&button->timer,
					  jiffies + DEBOUNCE_JIFFIES);
		}
	}

	schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES);
}

static int rk_key_type_get(struct device_node *node,
			   struct rk_keys_button *button)
{
	u32 adc_value;

	if (!of_property_read_u32(node, "rockchip,adc_value", &adc_value))
		return TYPE_ADC;
	else if (of_get_gpio(node, 0) >= 0)
		return TYPE_GPIO;
	else
		return -1;
}

static int rk_keys_parse_dt(struct rk_keys_drvdata *pdata,
			    struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct device_node *child_node;
	struct iio_channel *chan;
	int ret, gpio, i = 0;
	u32 code, adc_value, flags, drift;

	if (of_property_read_u32(node, "adc-drift", &drift))
		pdata->drift_advalue = DRIFT_DEFAULT_ADVALUE;
	else
		pdata->drift_advalue = (int)drift;

	chan = iio_channel_get(&pdev->dev, NULL);
	if (IS_ERR(chan)) {
		dev_info(&pdev->dev, "no io-channels defined\n");
		chan = NULL;
	}
	pdata->chan = chan;

	for_each_child_of_node(node, child_node) {
		if (of_property_read_u32(child_node, "linux,code", &code)) {
			dev_err(&pdev->dev,
				"Missing linux,code property in the DT.\n");
			ret = -EINVAL;
			goto error_ret;
		}
		pdata->button[i].code = code;
		pdata->button[i].desc =
		    of_get_property(child_node, "label", NULL);
		pdata->button[i].type =
		    rk_key_type_get(child_node, &pdata->button[i]);
		switch (pdata->button[i].type) {
		case TYPE_GPIO:
			gpio = of_get_gpio_flags(child_node, 0, &flags);
			if (gpio < 0) {
				ret = gpio;
				if (ret != -EPROBE_DEFER)
					dev_err(&pdev->dev,
						"Failed to get gpio flags, error: %d\n",
						ret);
				goto error_ret;
			}

			pdata->button[i].gpio = gpio;
			pdata->button[i].active_low =
			    flags & OF_GPIO_ACTIVE_LOW;
			pdata->button[i].wakeup =
			    !!of_get_property(child_node, "gpio-key,wakeup",
					      NULL);
			break;

		case TYPE_ADC:
			if (of_property_read_u32
			    (child_node, "rockchip,adc_value", &adc_value)) {
				dev_err(&pdev->dev,
					"Missing rockchip,adc_value property in the DT.\n");
				ret = -EINVAL;
				goto error_ret;
			}
			pdata->button[i].adc_value = adc_value;
			break;

		default:
			dev_err(&pdev->dev,
				"Error rockchip,type property in the DT.\n");
			ret = -EINVAL;
			goto error_ret;
		}
		i++;
	}

	return 0;

error_ret:
	return ret;
}

static int keys_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = pdev->dev.of_node;
	struct rk_keys_drvdata *ddata = NULL;
	struct input_dev *input = NULL;
	int i, error = 0;
	int wakeup, key_num = 0;

	key_num = of_get_child_count(np);
	if (key_num == 0)
		dev_info(&pdev->dev, "no key defined\n");

	ddata = devm_kzalloc(dev, sizeof(struct rk_keys_drvdata) +
			     key_num * sizeof(struct rk_keys_button),
			     GFP_KERNEL);

	input = devm_input_allocate_device(dev);
	if (!ddata || !input) {
		error = -ENOMEM;
		return error;
	}
	platform_set_drvdata(pdev, ddata);
	dev_set_drvdata(&pdev->dev, ddata);

	input->name = "rk29-keypad";	/* pdev->name; */
	input->phys = "gpio-keys/input0";
	input->dev.parent = dev;

	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;
	ddata->input = input;

	/* parse info from dt */
	ddata->nbuttons = key_num;
	error = rk_keys_parse_dt(ddata, pdev);
	if (error)
		goto fail0;

	/* Enable auto repeat feature of Linux input subsystem */
	if (ddata->rep)
		__set_bit(EV_REP, input->evbit);

	error = input_register_device(input);
	if (error) {
		pr_err("gpio-keys: Unable to register input device, error: %d\n",
		       error);
		goto fail0;
	}
	sinput_dev = input;

	for (i = 0; i < ddata->nbuttons; i++) {
		struct rk_keys_button *button = &ddata->button[i];

		if (button->code) {
			setup_timer(&button->timer,
				    keys_timer, (unsigned long)button);
		}

		if (button->wakeup)
			wakeup = 1;

		input_set_capability(input, EV_KEY, button->code);
	}

	wake_lock_init(&ddata->wake_lock, WAKE_LOCK_SUSPEND, input->name);
	device_init_wakeup(dev, wakeup);

	for (i = 0; i < ddata->nbuttons; i++) {
		struct rk_keys_button *button = &ddata->button[i];

		button->dev = &pdev->dev;
		if (button->type == TYPE_GPIO) {
			int irq;

			error =
			    devm_gpio_request(dev, button->gpio,
					      button->desc ? : "keys");
			if (error < 0) {
				pr_err("gpio-keys: failed to request GPIO %d, error %d\n",
				       button->gpio, error);
				goto fail1;
			}

			error = gpio_direction_input(button->gpio);
			if (error < 0) {
				pr_err("gpio-keys: failed to configure input direction for GPIO %d, error %d\n",
				       button->gpio, error);
				gpio_free(button->gpio);
				goto fail1;
			}

			irq = gpio_to_irq(button->gpio);
			if (irq < 0) {
				error = irq;
				pr_err("gpio-keys: Unable to get irq number for GPIO %d, error %d\n",
				       button->gpio, error);
				gpio_free(button->gpio);
				goto fail1;
			}

			error = devm_request_irq(dev, irq, keys_isr,
						 button->active_low ?
						 IRQF_TRIGGER_FALLING :
						 IRQF_TRIGGER_RISING,
						 button->desc ?
						 button->desc : "keys",
						 button);
			if (error) {
				pr_err("gpio-keys: Unable to claim irq %d; error %d\n",
				       irq, error);
				gpio_free(button->gpio);
				goto fail1;
			}
		}
	}

	input_set_capability(input, EV_KEY, KEY_WAKEUP);
	/* adc polling work */
	if (ddata->chan) {
		INIT_DELAYED_WORK(&ddata->adc_poll_work, adc_key_poll);
		schedule_delayed_work(&ddata->adc_poll_work,
				      ADC_SAMPLE_JIFFIES);
	}

	return error;

fail1:
	while (--i >= 0)
		del_timer_sync(&ddata->button[i].timer);
	device_init_wakeup(dev, 0);
	wake_lock_destroy(&ddata->wake_lock);
fail0:
	platform_set_drvdata(pdev, NULL);

	return error;
}

static int keys_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct rk_keys_drvdata *ddata = dev_get_drvdata(dev);
	struct input_dev *input = ddata->input;
	int i;

	device_init_wakeup(dev, 0);
	for (i = 0; i < ddata->nbuttons; i++)
		del_timer_sync(&ddata->button[i].timer);
	if (ddata->chan)
		cancel_delayed_work_sync(&ddata->adc_poll_work);
	input_unregister_device(input);
	wake_lock_destroy(&ddata->wake_lock);

	sinput_dev = NULL;

	return 0;
}

#ifdef CONFIG_PM
static int keys_suspend(struct device *dev)
{
	struct rk_keys_drvdata *ddata = dev_get_drvdata(dev);
	int i;

	ddata->in_suspend = true;
	if (device_may_wakeup(dev)) {
		for (i = 0; i < ddata->nbuttons; i++) {
			struct rk_keys_button *button = ddata->button + i;

			if (button->wakeup)
				enable_irq_wake(gpio_to_irq(button->gpio));
		}
	}

	return 0;
}

static int keys_resume(struct device *dev)
{
	struct rk_keys_drvdata *ddata = dev_get_drvdata(dev);
	int i;

	if (device_may_wakeup(dev)) {
		for (i = 0; i < ddata->nbuttons; i++) {
			struct rk_keys_button *button = ddata->button + i;

			if (button->wakeup)
				disable_irq_wake(gpio_to_irq(button->gpio));
		}
		preempt_disable();
		/* for call resend_irqs, which may call keys_isr */
		if (local_softirq_pending())
			do_softirq();
		preempt_enable_no_resched();
	}

	ddata->in_suspend = false;

	return 0;
}

static const struct dev_pm_ops keys_pm_ops = {
	.suspend	= keys_suspend,
	.resume		= keys_resume,
};
#endif

static struct platform_driver keys_device_driver = {
	.probe		= keys_probe,
	.remove		= keys_remove,
	.driver		= {
		.name	= "rk-keypad",
		.owner	= THIS_MODULE,
		.of_match_table = rk_key_match,
#ifdef CONFIG_PM
		.pm	= &keys_pm_ops,
#endif
	}
};

static int __init rk_keys_driver_init(void)
{
	return platform_driver_register(&keys_device_driver);
}

static void __exit rk_keys_driver_exit(void)
{
	platform_driver_unregister(&keys_device_driver);
}

late_initcall_sync(rk_keys_driver_init);
module_exit(rk_keys_driver_exit);

        3.3 dts 通过io-channels = <&saradc 2>; 设置通道

saradc: saradc@ff288000 {
		compatible = "rockchip,px30-saradc", "rockchip,rk3399-saradc";
		reg = <0x0 0xff288000 0x0 0x100>;
		interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
		#io-channel-cells = <1>;
		clocks = <&cru SCLK_SARADC>, <&cru PCLK_SARADC>;
		clock-names = "saradc", "apb_pclk";
		resets = <&cru SRST_SARADC_P>;
		reset-names = "saradc-apb";

	};

adc-keys {
		compatible = "adc-keys";
		io-channels = <&saradc 2>;
		io-channel-names = "buttons";
		poll-interval = <100>;
		keyup-threshold-microvolt = <1800000>;

		esc-key {
			linux,code = <KEY_ESC>;
			label = "esc";
			press-threshold-microvolt = <1310000>;
		};

		home-key {
			linux,code = <KEY_HOME>;
			label = "home";
			press-threshold-microvolt = <624000>;
		};

		menu-key {
			linux,code = <KEY_MENU>;
			label = "menu";
			press-threshold-microvolt = <987000>;
		};

		vol-down-key {
			linux,code = <KEY_VOLUMEDOWN>;
			label = "volume down";
			press-threshold-microvolt = <300000>;
		};

		vol-up-key {
			linux,code = <KEY_VOLUMEUP>;
			label = "volume up";
			press-threshold-microvolt = <17000>;
		};
	};

        3.4  相关的iio 节点在/sys/devices/platform/ff288000.saradc/iio:device1/路径下面,可以直接cat获取相关值

2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。,学习

四、我自己写的一个demo。

        4.1 注册 iio 接口 dac8xxxx.c 驱动,里面会使用iio_device_register 进行注册。

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/property.h>
#include <linux/pm_runtime.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>

/*
****************************************
0h NOOP No operation NOOP Register
1h DEVID Device identification DEVID Register
2h SYNC Synchronization SYNC Register
3h CONFIG Configuration CONFIG Register
4h GAIN Gain GAIN Register
5h TRIGGER Trigger TRIGGER Register
7h STATUS Status STATUS Register
8h DAC
****************************************
*/
#define   NOOP_REG      0x00
#define   DEVID_REG     0x01
#define   SYNC_REG      0x02
#define   CONFIG_REG    0x03
#define   GAIN_REG      0x04
#define   TRIGGER_REG   0x05
#define   STATUS_REG    0x07
#define   DAC_REG       0x08
#define   DAC80501Z_ID           0X0115
#define   DAC80501_DRV_NAME    "dac80501"

#define   DAC80501Z_INTERNAL_REFERENCE_VOLTAGE       2500 * 1000
#define   DAC80501Z_EXTERNAL_REFERENCE_VOLTAGE       2500 * 1000

#define   DAC80501_GAIN_REG_BUFF_GAIN_MSK	          BIT(0)
#define   DAC80501_GAIN_REG_BUFF_GAIN_TWO		      BIT(0)
#define   DAC80501_GAIN_REG_BUFF_GAIN_ONE			  (0)


#define   DAC80501_CONFIG_REG_REF_PWDWN_MSK           BIT(8)
#define   DAC80501_CONFIG_REG_OUTSIDE_REFERENCE       BIT(8)
#define   DAC80501_CONFIG_REG_INTERNAL_REFERENCE      0

#define DAC80501_IOCTL_MAGIC		'L'
#define CMD_DAC_VOLTAGE_OUTPUT          _IOW(DAC80501_IOCTL_MAGIC, 2, int)
#define CMD_DAC_GAIN_REG_BUFF_GAIN      _IOW(DAC80501_IOCTL_MAGIC, 3, int)
#define CMD_WRITE_DAC_REG_VALUE             _IOW(DAC80501_IOCTL_MAGIC, 4, int)
#define CMD_READ_DAC_REG_VALUE             _IOW(DAC80501_IOCTL_MAGIC, 5, int)


struct regmap *regmap;
struct iio_channel *chan;
static struct i2c_client *dac80501_i2c_client;

#define ADS_CHANNEL(_index, _id) {				\
	.type = IIO_VOLTAGE,					\
	.indexed = 1,						\
	.channel = _index,					\
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
	.datasheet_name = _id,					\
}

static const struct iio_chan_spec voltage_channel[] = {
	 ADS_CHANNEL(0, "ads"),
     ADS_CHANNEL(1, "ads"),

     /*{
		//.type = IIO_VOLTAGE,
		//.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
		   .channel = 0,	
		  .type = IIO_VOLTAGE,
	      .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
	      .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
		},*/

};

struct regval {
	unsigned char  addr;
	unsigned int   val;
};

struct  dac80501_data{
    struct i2c_client   *client;
    struct regmap       *regmap;
    //struct regmap_config regmap_cfg;
    struct mutex         lock;
};

static const struct regval dac80501_regs[] = {
	{NOOP_REG    , 0x00},
	{SYNC_REG    , 0x00},
	{CONFIG_REG  , 0x00},
	{GAIN_REG    , 0x01},
	{TRIGGER_REG , 0x00},
	{STATUS_REG  , 0x00},
	{DAC_REG     , 0x00},
};

static struct regmap_config dac80501_regmap_config = {
    .reg_bits = 8,
    .val_bits = 16,
    .max_register = DAC_REG,
};

static int dac80501_drv_open(struct inode *inode, struct file *file)
{
    //printk("[%s]\r\n",__func__);
	return 0;
}
ssize_t dac80501_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
       printk("[%s]\r\n",__func__);
	   return size;
}

ssize_t dac80501_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{

	char kbuf[10] = {0};
	int reg_value=0 ,gain_value = 0;
	int  value;
	if (copy_from_user(kbuf, ubuf, size) != 0)
	{
	    printk("copy_from_user error \n "); 
		return -1;
	}
	sscanf(kbuf, "%d",&value);
	printk("[%s] value = %d\r\n",__func__,value );

	if(value > DAC80501Z_INTERNAL_REFERENCE_VOLTAGE){
		regmap_update_bits(regmap, GAIN_REG,DAC80501_GAIN_REG_BUFF_GAIN_MSK, 
								DAC80501_GAIN_REG_BUFF_GAIN_TWO);
	}
	regmap_read(regmap, GAIN_REG, &reg_value);
	gain_value = (reg_value & DAC80501_GAIN_REG_BUFF_GAIN_TWO) > 0	? 2 : 1;
	reg_value = (value * 0XFFFF) / (DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * gain_value);
	regmap_write(regmap, DAC_REG, reg_value);
	pr_info("GAIN_REG reg_value=0X%X\n",reg_value);

	return size;
}

long dac80501_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
	int reg_value=0 ,gain_value = 0;
	long temp = 0,temp_gain_value;
	void __user *argp = (void __user *)value;

	switch (cmd)
	{
	     case CMD_DAC_VOLTAGE_OUTPUT:
			if(value > DAC80501Z_INTERNAL_REFERENCE_VOLTAGE){
				regmap_update_bits(regmap, GAIN_REG,DAC80501_GAIN_REG_BUFF_GAIN_MSK, 
										DAC80501_GAIN_REG_BUFF_GAIN_TWO);
			}
			regmap_read(regmap, GAIN_REG, &reg_value);
			gain_value = (reg_value & DAC80501_GAIN_REG_BUFF_GAIN_TWO) > 0	? 2 : 1;

			if(value >= DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * gain_value)
				reg_value = 0XFFFF;
			else{
				temp_gain_value = gain_value;
			    temp = (value * 0XFFFF) / (DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * temp_gain_value);
                reg_value = temp;
			    //reg_value = (value * 0XFFFF) / (DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * gain_value);
			}
			regmap_write(regmap, DAC_REG, reg_value);
			//pr_info("dac80501_ioctl  value=%ld,temp = 0X%lX,reg_value=0X%X\n",value,temp,reg_value);
			break;
        case CMD_WRITE_DAC_REG_VALUE:
		      regmap_write(regmap, DAC_REG, value);
	  	      break;
	    case CMD_READ_DAC_REG_VALUE:
		      regmap_read(regmap, DAC_REG, &reg_value);
		      if (copy_to_user(argp , &reg_value , sizeof(reg_value)))
				return -EFAULT;
		      break;	
	   case CMD_DAC_GAIN_REG_BUFF_GAIN:
			if(value == 1)
				regmap_update_bits(regmap, GAIN_REG,DAC80501_GAIN_REG_BUFF_GAIN_MSK, 
										DAC80501_GAIN_REG_BUFF_GAIN_TWO);
            else
				regmap_update_bits(regmap, GAIN_REG,DAC80501_GAIN_REG_BUFF_GAIN_MSK, 
										DAC80501_GAIN_REG_BUFF_GAIN_ONE);

		    break;
	}
	return 0;
}

static struct file_operations dac80501_drv_fops = {
	.owner		     = THIS_MODULE,
	.open		     = dac80501_drv_open,
    .write		     = dac80501_write,
    .read            = dac80501_read,
    .unlocked_ioctl  = dac80501_ioctl, 
};

static struct miscdevice dac80501_miscdev = 
{
	.minor	        = MISC_DYNAMIC_MINOR,
   	.name	        = DAC80501_DRV_NAME,
    .fops	        = &dac80501_drv_fops,
};

static ssize_t dac80501_dbg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	int ret,reg_offset,reg_data;
	//struct dac80501_data *data = i2c_get_clientdata(rk808_i2c_client);

	unsigned char  iten_str[20] , total_reg_data_str[150] = {0};

    for( reg_offset = 0 ; reg_offset <= DAC_REG ; reg_offset++){
	   if(reg_offset == 0x06)
	    	continue;
	   ret = regmap_read(regmap, reg_offset, &reg_data);
	   sprintf(iten_str, "[0x%02x]=0x%04x\r\n", reg_offset,reg_data);
	   printk("%s\n", iten_str);
       strcat(total_reg_data_str, iten_str);
   }
	return sprintf(buf, "%s\n", total_reg_data_str);
}

static ssize_t dac80501_dbg_store(struct device *dev, struct device_attribute *attr,
	   const char *buf, size_t count)
{
	  unsigned int ret ,reg_addr,reg_value;
	  ret = sscanf(buf, "%x %x ", &reg_addr, &reg_value);
      regmap_write(regmap, reg_addr, reg_value);
	  printk("[%s]ret=%d,reg_addr=0x%02x,reg_value=0x%04x\r\n",__func__,ret,reg_addr,reg_value);

	  return count;
}

static DEVICE_ATTR(dac80501_dbg,    0644, dac80501_dbg_show, dac80501_dbg_store);

int dac80501_iio_readraw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
		int *val, int *val2, long mask) {
	pr_info("myiio_readraw start\n");
	pr_info("myiio_readraw: iio_dev.name = %s\n", indio_dev->name);
	pr_info("myiio_readraw: iio_dev.type = %d\n", chan->type);
	pr_info("myiio_readraw: channel= %d,channel2 = %d", chan->channel,chan->channel2);
	pr_info("myiio_readraw: mask = %ld", mask);


	switch (mask) {
	case IIO_CHAN_INFO_PROCESSED:
		pr_info("mask = IIO_CHAN_INFO_PROCESSED\n");
		break;
	default:
		pr_info("who are you \n");
		break;
	}
	*val = 389;
	*val2 = 52;
	pr_info("dac80501_iio_readraw  end\n");
	return IIO_VAL_FRACTIONAL;
}

static int dac80501_iio_writeraw(struct iio_dev *indio_dev,
			 struct iio_chan_spec const *chan,int val,int val2,long mask)
{
    int  ret = 0 ;
	int  reg_value=0 ,gain_value = 0;
	long temp = 0,temp_val,temp_gain_value;
    struct dac80501_data *data = iio_priv(indio_dev);
    
    switch (mask) {
	    case IIO_CHAN_INFO_PROCESSED:
			if(val > DAC80501Z_INTERNAL_REFERENCE_VOLTAGE){
				regmap_update_bits(data->regmap, GAIN_REG,DAC80501_GAIN_REG_BUFF_GAIN_MSK, 
				                        DAC80501_GAIN_REG_BUFF_GAIN_TWO);
			}
			regmap_read(data->regmap, GAIN_REG, &reg_value);
			gain_value = (reg_value & DAC80501_GAIN_REG_BUFF_GAIN_TWO) > 0  ? 2 : 1;
			if(val >= DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * gain_value)
				reg_value = 0XFFFF;
			else{
				temp_val = val;
				temp_gain_value = gain_value;
			    temp = (temp_val * 0XFFFF) / (DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * temp_gain_value);
				//reg_value = (val * 0XFFFF) / (DAC80501Z_INTERNAL_REFERENCE_VOLTAGE * gain_value);
                reg_value = temp;
			}
			regmap_write(data->regmap, DAC_REG, reg_value);

			break;
	    default:
	        ret = -EINVAL;
			pr_info("EINVAL\n");
	        break;
    }

	pr_info("myiio_writeraw end  val=%d,val2=%d,mask=%ld temp = 0X%lX,reg_value=0X%X\n",val,val2,mask,temp,reg_value);
    return ret;
}    


static const struct iio_info info = { 
      .driver_module = THIS_MODULE, 
	  .write_raw = dac80501_iio_writeraw, 
	  .read_raw   = dac80501_iio_readraw, 
};

static int dac80501_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret ,i;
	unsigned int chip_id;
	struct iio_dev *iiodev;
	struct dac80501_data *data;
	struct device *dev = &client->dev;
	printk("[%s]",__func__);

    iiodev = devm_iio_device_alloc(&client->dev, sizeof(struct dac80501_data));
    if (!iiodev) {
        return -ENOMEM;
    }
	
	data = iio_priv(iiodev);
	data->client = client;
	i2c_set_clientdata(client, iiodev);

    iiodev->name = "dac80501";
    iiodev->dev.parent = &client->dev;
    iiodev->info = &info;
    iiodev->modes = INDIO_DIRECT_MODE;
    iiodev->channels = voltage_channel;
    iiodev->num_channels = ARRAY_SIZE(voltage_channel);

    ret = iio_device_register(iiodev);

    if (ret < 0) {
        dev_err(&client->dev, "iio_device_register failed\n");
        goto err_iio_register;
    }

    data->regmap = regmap_init_i2c(client, &dac80501_regmap_config);
    if (IS_ERR(data->regmap)) {
        ret = PTR_ERR(data->regmap);
        goto err_regmap_init;
    }
	
	dac80501_i2c_client = client;
	regmap = data->regmap;

	ret = regmap_read(data->regmap, DEVID_REG, &chip_id);
    if(DAC80501Z_ID == chip_id){
	    printk("[%s] ret=%d , chip_id = 0X%x \r\n",__func__,ret,chip_id);
    }else{
		dev_err(dev, "failed to read dac80501Z chip id\n");
		return -ENOMEM;
    }
	
    for(i =0 ; i< sizeof(dac80501_regs) / sizeof(dac80501_regs[0]) ; i++){
	    ret = regmap_write(data->regmap, dac80501_regs[i].addr, dac80501_regs[i].val);
	    printk("[%s] ret=%d; dac80501_regs[i].addr = 0x%x, dac80501_regs[i].val = 0x%x\r\n",__func__,
	         ret,dac80501_regs[i].addr, dac80501_regs[i].val);
    }
   	ret = device_create_file(&client->dev, &dev_attr_dac80501_dbg);
	ret = misc_register(&dac80501_miscdev); 

    return 0;

err_regmap_init:
    iio_device_unregister(iiodev);
err_iio_register:

    regmap_exit(data->regmap);
    return ret;
	
}

static int dac80501_remove(struct i2c_client *client)
{
    // 注销设备
    return 0;
}
static const struct of_device_id dac80501_match[] = {
	{ .compatible = "topdon,dac80501" },
	{ },
};

static const struct i2c_device_id dac80501_id[] = {
    { "dac80501_driver", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, dac80501_id);

static struct i2c_driver dac80501_driver = {
    .driver = {
        .name = DAC80501_DRV_NAME,
		.of_match_table = dac80501_match,
        .owner = THIS_MODULE,
    },
    .id_table = dac80501_id,
    .probe = dac80501_probe,
    .remove = dac80501_remove,
};

static int __init dac80501_init(void)
{
	printk("[%s]\r\n",__func__);
    return i2c_add_driver(&dac80501_driver);
}

static void __exit dac80501_exit(void)
{
    i2c_del_driver(&dac80501_driver);
}

module_init(dac80501_init);
module_exit(dac80501_exit);

MODULE_AUTHOR("terry");
MODULE_DESCRIPTION("dac80501 driver");
MODULE_LICENSE("GPL");

        4.2 在另外一个驱动里面调用iio接口,主要的代码是

struct iio_channel *chan;	  //#定义 IIO 通道结构体
	int val,ret;

	chan = iio_channel_get(&client->dev, NULL);	// #获取 IIO 通道结构体
	if (IS_ERR(chan)){
		chan = NULL;
		printk("%s() have not set adcchan d\n", __FUNCTION__);
		
	}else{
		printk("%s() have set adcchan d\n", __FUNCTION__);
		
        ret = iio_read_channel_raw(chan, &val);
			printk("ret = %d  val = %d",ret,val);
}
/*
 * ads1119.c - lm_sensors driver for ads1119 16-bit 4-input ADC
 * (C) Copyright 2010
 * Dirk Eibach, Guntermann & Drunck GmbH <eibach@gdsys.de>
 *
 * Based on the ads7828 driver by Steve Hardy.
 *
 * Datasheet available at: http://focus.ti.com/lit/ds/symlink/ads1015.pdf
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
#include <linux/iio/consumer.h>


#define DEVICE_NAME       "ads1119"
#define INTERNALREFERENCE    2.048 

#define ADS1119_CHANNELS           4
#define ADS1119_CFG_REG            0x01
#define ADS1119_DEFAULT_GAIN       0x01
#define ADS1119_DEFAULT_CHANNELS   0xff
#define ADS1119_DEFAULT_DATA_RATE  330
#define ADS1119_STATUS_REG_DRDY	          BIT(7)
#define ADS1119_CONF_REG_GAIN_BIT	      BIT(4)


#define ADS1119_IOCTL_MAGIC		'A'
#define ADS1119_IOCTL_WRITE		                    _IOW(ADS1119_IOCTL_MAGIC, 0x01, char*)
#define ADS1119_IOCTL_READ_VOLTAGE  	            _IOWR(ADS1119_IOCTL_MAGIC, 0x02, char*)
#define ADS1119_READ_RESI_PARTIAL_CONVERSION_REG  	_IOWR(ADS1119_IOCTL_MAGIC, 0x03, char*)
#define ADS1119_READ_DAC_VOUT_CONVERSION_REG  	    _IOWR(ADS1119_IOCTL_MAGIC, 0x04, char*)

struct ads1119_channel_data {
	bool enabled;
	unsigned int gain;
	unsigned int data_rate;
};

struct ads1119_platform_data {
	struct ads1119_channel_data channel_data[ADS1119_CHANNELS];
};

/*
 * RESET Reset the device 0000 011x
 * START/SYNC Start or restart conversions 0000 100x
 * POWERDOWN Enter power-down mode 0000 001x
 * RDATA Read data by command 0001 xxxx
 * RREG Read register at address r 0010 0rxx
 * WREG Write configuration register 0100 00xx
 */

/* ADS1119 registers */
enum {
	REG_RESET       = 0X06,
	REG_STRAT       = 0X08,
	REG_POWERDOWN   = 0X02,
	REG_RDATA       = 0X10,
	REG_CONF_RREG   = 0X20,//Configuration
	REG_STATUS_RREG = 0X24,
	REG_WREG        = 0X40,
};

struct i2c_client *ads1119_i2c_client;

/* Data rates in samples per second */

static const unsigned int data_rate_table_1119[4] = {
	20, 90, 330, 1000
};

enum ads1119_chips {
	ads1015,
	ads1119,
};
	
struct ads1119_data {
	struct device *hwmon_dev;
	struct mutex update_lock; /* mutex protect updates */
	struct ads1119_channel_data channel_data[ADS1119_CHANNELS];
	enum ads1119_chips id;
	int reset_gpio, active_low;
	u32 delays[3];
};

static int ads1119_read_adc(struct i2c_client *client, unsigned int channel)
{
	int res,i;
	u8  status,config = 0;
	struct ads1119_data *data = i2c_get_clientdata(client);
	unsigned int gain = data->channel_data[channel].gain;
	unsigned int data_rate = data->channel_data[channel].data_rate;
	unsigned int conversion_time_ms;
	const unsigned int * const rate_table = data_rate_table_1119;

	mutex_lock(&data->update_lock);
	/* get config parameters */
	config |= (channel + 0x03) << 5;
	config = (gain == 4) ? (config | ADS1119_CONF_REG_GAIN_BIT ): config;	

    for(i = 0; i < sizeof(data_rate_table_1119) / sizeof(data_rate_table_1119[0]) ; i++){
		if(rate_table[i] == data_rate)
		{
		   config |= i << 2;
		   break;
		}
    }
	
	res = i2c_smbus_write_byte_data(client, REG_WREG , config );
	if (res < 0)
		goto err_unlock;
	
	res = i2c_smbus_write_byte(client, REG_STRAT);
	if (res < 0)
		goto err_unlock;
	
	conversion_time_ms = DIV_ROUND_UP(1000, data_rate);
	/* wait until conversion finished */
	i = 0;
	do{
		msleep(conversion_time_ms);
		res = i2c_smbus_read_byte_data(client, REG_STATUS_RREG);
		printk("[%s] read REG_STATUS_RREG,conversion reg = 0x%x ,conversion_time_ms=%d,gain = %d,data_rate = %d\r\n",
			                            __func__,res,conversion_time_ms,gain,data_rate);
		if (res < 0)
			goto err_unlock;

		status = res;
		if (!(status & ADS1119_STATUS_REG_DRDY)) {
			/* conversion not finished in time */
			if(i > 3){
				res = -EIO;
			    goto err_unlock;
			}
		}else 
			break;
		i++;
	}while(true);
	 
	res = i2c_smbus_read_word_swapped(client, REG_RDATA);
	if (res < 0)
		goto err_unlock;
	
	printk("[%s] config = 0x%x,conversion reg data = 0x%x \r\n",__func__,config,res);
	
    goto succeed;

err_unlock:
	printk("[%s] Failed to adc value,res = %d\r\n",__func__,res);
succeed:
	mutex_unlock(&data->update_lock);
	return res;
}

static long ads1119_reg_to_uv(struct i2c_client *client, unsigned int channel,
			     s16 reg)
{
	//struct ads1119_data *data = i2c_get_clientdata(client);
	//unsigned int pga = data->channel_data[channel].pga;
	long fullscale = INTERNALREFERENCE * 1000 * 1000;
	const int mask = 0X7FFF;

	return DIV_ROUND_CLOSEST(reg * fullscale, mask);
}

/* sysfs callback function */
static ssize_t show_in(struct device *dev, struct device_attribute *da,
	char *buf)
{
	struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
	struct i2c_client *client = to_i2c_client(dev);
	int res;
	int index = attr->index;
	struct iio_channel *chan;	  //#定义 IIO 通道结构体
	int val,ret;

	chan = iio_channel_get(&client->dev, NULL);	// #获取 IIO 通道结构体
	if (IS_ERR(chan)){
		chan = NULL;
		printk("%s() have not set adcchan d\n", __FUNCTION__);
		
	}else{
		printk("%s() have set adcchan d\n", __FUNCTION__);
		
        ret = iio_read_channel_raw(chan, &val);
			printk("ret = %d  val = %d",ret,val);
		}

	res = ads1119_read_adc(client, index);
	if (res < 0)
		return res;

	return sprintf(buf, "%ld\n", ads1119_reg_to_uv(client, index, res));
}

static const struct sensor_device_attribute ads1119_in[] = {
	SENSOR_ATTR(ads_in0_input, S_IRUGO, show_in, NULL, 0),
	SENSOR_ATTR(ads_in1_input, S_IRUGO, show_in, NULL, 1),
	SENSOR_ATTR(ads_in2_input, S_IRUGO, show_in, NULL, 2),
	SENSOR_ATTR(ads_in3_input, S_IRUGO, show_in, NULL, 3),
};

static int ads1119_remove(struct i2c_client *client)
{
	struct ads1119_data *data = i2c_get_clientdata(client);
	int k;

	hwmon_device_unregister(data->hwmon_dev);
	for (k = 0; k < ADS1119_CHANNELS; ++k)
		device_remove_file(&client->dev, &ads1119_in[k].dev_attr);
	return 0;
}

static int ads1119_reset_chip(struct i2c_client *client)
{
	struct ads1119_data *data = i2c_get_clientdata(client);

    if (devm_gpio_request(&client->dev, data->reset_gpio,"ads1119-reset"))
	    return -EINVAL;
	
	gpio_direction_output(data->reset_gpio,
				  data->active_low ? 1 : 0);
	if (data->delays[0])
		msleep(DIV_ROUND_UP(data->delays[0], 1000));
	
	gpio_set_value(data->reset_gpio, data->active_low ? 0 : 1);
	if (data->delays[1])
		msleep(DIV_ROUND_UP(data->delays[1], 1000));
	
	gpio_set_value(data->reset_gpio, data->active_low ? 1 : 0);
	if (data->delays[2])
		msleep(DIV_ROUND_UP(data->delays[2], 1000));
	
	return 0;
}

static int ads1119_get_dts_config_of(struct i2c_client *client)
{
	struct ads1119_data *data = i2c_get_clientdata(client);
	struct device_node *node;
	struct device_node *np = client->dev.of_node;

	if (!client->dev.of_node
	    || !of_get_next_child(client->dev.of_node, NULL))
		return -EINVAL;

	data->reset_gpio = of_get_named_gpio(np,
							"reset-gpio", 0);
	if (data->reset_gpio < 0)
		return -EINVAL;
	
    data->active_low = of_property_read_bool(np,
							"reset-active-low");
	of_property_read_u32_array(np,
					"reset-delays-us", data->delays, 3);

	for_each_child_of_node(client->dev.of_node, node) {
		u32 pval;
		unsigned int channel;
		unsigned int gain = ADS1119_DEFAULT_GAIN;
		unsigned int data_rate = ADS1119_DEFAULT_DATA_RATE;

		if (of_property_read_u32(node, "reg", &pval)) {
			dev_err(&client->dev, "invalid reg on %s\n",
				node->full_name);
			continue;
		}

		channel = pval;
		if (channel >= ADS1119_CHANNELS) {
			dev_err(&client->dev,
				"invalid channel index %d on %s\n",
				channel, node->full_name);
			continue;
		}

		if (!of_property_read_u32(node, "ti,gain", &pval)) {
			gain = pval;
			if (gain > 4) {
				dev_err(&client->dev, "invalid gain on %s\n",
					node->full_name);
				return -EINVAL;
			}
		}

		if (!of_property_read_u32(node, "ti,datarate", &pval)) {
			data_rate = pval;
			if (data_rate > 1000) {
				dev_err(&client->dev,
					"invalid data_rate on %s\n",
					node->full_name);
				return -EINVAL;
			}
		}

		data->channel_data[channel].enabled = true;
		data->channel_data[channel].gain = gain;
		data->channel_data[channel].data_rate = data_rate;
	}

	return 0;
}

static void ads1119_get_channels_config(struct i2c_client *client)
{
	unsigned int k;
	struct ads1119_data *data = i2c_get_clientdata(client);
	struct ads1119_platform_data *pdata = dev_get_platdata(&client->dev);

	/* prefer platform data */
	if (pdata) {
		memcpy(data->channel_data, pdata->channel_data,
		       sizeof(data->channel_data));
		return;
	}

	if (!ads1119_get_dts_config_of(client)){
		printk("ads1119 succeeded to get channels config \n");
		return;
	}else
	    dev_warn(&client->dev,"ads1119 Failed to get channels config \n");

	/* fallback on default configuration */
	for (k = 0; k < ADS1119_CHANNELS; ++k) {
		data->channel_data[k].enabled = true;
		data->channel_data[k].gain = ADS1119_DEFAULT_GAIN;
		data->channel_data[k].data_rate = ADS1119_DEFAULT_DATA_RATE;
	}
}

static int ads1119_open(struct inode *inode, struct file *file)
{
	return 0;
}
ssize_t ads1119_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk("[%s]\r\n",__func__);
	return sprintf(buf, "%d\n", __LINE__);
}

ssize_t ads1119_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
	printk("[%s]\r\n",__func__);
	return size;
}

long ads1119_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int res;
	unsigned long ads_voltage_value_us = 0;
	void __user *argp = (void __user *)arg;
	
	switch (cmd) {
		case ADS1119_IOCTL_READ_VOLTAGE:
			if (!argp)
				return -EINVAL;
			res = ads1119_read_adc(ads1119_i2c_client, 0);
			if (res < 0)
				return res;
			ads_voltage_value_us  = ads1119_reg_to_uv(ads1119_i2c_client, 0, res);
			if (copy_to_user(argp , &ads_voltage_value_us , sizeof(ads_voltage_value_us)))
							return -EFAULT;

			break;
		case ADS1119_READ_RESI_PARTIAL_CONVERSION_REG:
			if (!argp)
				return -EINVAL;
			res = ads1119_read_adc(ads1119_i2c_client, 0);
			if (res < 0)
				return res;
			if (copy_to_user(argp , &res , sizeof(res)))
				return -EFAULT;

		    break;
		case ADS1119_READ_DAC_VOUT_CONVERSION_REG:
			if (!argp)
				return -EINVAL;
			res = ads1119_read_adc(ads1119_i2c_client, 1);
			if (res < 0)
				return res;
			if (copy_to_user(argp , &res , sizeof(res)))
				return -EFAULT;

		    break;	
		default:
				
				return -EFAULT;
				break;
	}

	return 0;
}

static struct file_operations ads1119_drv_fops = {
	.owner		     = THIS_MODULE,
	.open		     = ads1119_open,
    .write		     = ads1119_write,
    .read            = ads1119_read,
    .unlocked_ioctl  = ads1119_ioctl, 
};

static struct miscdevice ads1119_miscdev = 
{
	.minor	        = MISC_DYNAMIC_MINOR,
   	.name	        = DEVICE_NAME,
    .fops	        = &ads1119_drv_fops,
};

static int ads1119_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct ads1119_data *data;
	int err ,ret;
	unsigned int k;

    printk("[%s]",__func__);
	data = devm_kzalloc(&client->dev, sizeof(struct ads1119_data),
			    GFP_KERNEL);
	if (!data)
		return -ENOMEM;
	data->id = ads1119;
	i2c_set_clientdata(client, data);
	mutex_init(&data->update_lock);

	/* build sysfs attribute group */
	ads1119_get_channels_config(client);
	ads1119_reset_chip(client);
	for (k = 0; k < ADS1119_CHANNELS; ++k) {
		if (!data->channel_data[k].enabled)
			continue;
		err = device_create_file(&client->dev, &ads1119_in[k].dev_attr);
		if (err)
			goto exit_remove;
	}
    for(k = 0;k < 5; k++){
	  ret = i2c_smbus_read_byte_data(client, REG_STATUS_RREG);
	  if(ret >= 0)
	  	break;
	  else 
	    msleep(10);
    }
	
	if (ret < 0){
		dev_warn(&client->dev,"Failed to detect ads1119 \n");
		//goto exit_remove;
	}

	data->hwmon_dev = hwmon_device_register(&client->dev);
	if (IS_ERR(data->hwmon_dev)) {
		err = PTR_ERR(data->hwmon_dev);
		goto exit_remove;
	}
	ret = misc_register(&ads1119_miscdev); 
	ads1119_i2c_client = client;

	return 0;

exit_remove:
	for (k = 0; k < ADS1119_CHANNELS; ++k)
		device_remove_file(&client->dev, &ads1119_in[k].dev_attr);
	return -EINVAL;
}

static const struct of_device_id ads1119_match[] = {
	{ .compatible = "topdon,ads1119" },
	{ },
};

static const struct i2c_device_id ads1119_id[] = {
	{ "ads1119",  ads1119},
	{ }
};
MODULE_DEVICE_TABLE(i2c, ads1119_id);

static struct i2c_driver ads1119_driver = {
	.driver = {
		.name = "ads1119",
		.of_match_table = ads1119_match,
        .owner = THIS_MODULE,
	},
	.probe = ads1119_probe,
	.remove = ads1119_remove,
	.id_table = ads1119_id,
};

module_i2c_driver(ads1119_driver);

MODULE_AUTHOR("Dirk Eibach <eibach@gdsys.de>");
MODULE_DESCRIPTION("ADS1119 driver");
MODULE_LICENSE("GPL");

        4.3 dts ,主要是添加io-channels = <&dac80501  1>;

   dacxxx:dacxxx@48 {
         compatible = "topdon,dacxxx";
         #io-channel-cells = <1>;
         reg = <0x48>;
    };
    adsxxx@40 {
         status = "okay";
         compatible = "topdon,adsxxx";
         reg = <0x40>;
         reset-gpio = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>;
         reset-active-low;
	     reset-delays-us = <0 50000 50000>;
         #address-cells = <1>;
         io-channels = <&dac80501  1>; //saradc 5

         4.4 直接用cat 命令也是可以读iio节点

2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。,学习

        4.5 通过iio_read_channel_raw函数读iio接口

2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。,学习

五、参考文章

Linux学习笔记(22.2)——基于IIC + Regmap + IIO的AP3216C的设备驱动_linux regmap i2c_glen_cao的博客-CSDN博客

RK356X ADC 使用_iio_read_channel_raw_悲伤的小强的博客-CSDN博客

史上最简单的Linux内核IIO子系统入门demo_内核版本4.4.194_hehui0921的博客-CSDN博客

linux IIO子系统使用说明_zimu-zimu的博客-CSDN博客

史上最简单的Linux内核IIO子系统入门demo_内核版本4.4.194_hehui0921的博客-CSDN博客

linux kernel iio 架构_iio子系统介绍_小武~的博客-CSDN博客

linux IIO子系统使用说明

Linux設備驅動之IIO子系統——IIO框架數據讀取 - JavaShuo文章来源地址https://www.toymoban.com/news/detail-582427.html

到了这里,关于2023-07-10 linux IIO子系统使用学习,在TI 的ads1015驱动里面看到相关使用,故花点时间进行简单的学习,入门级别,纪录点滴。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Win10安装Linux子系统WSL(ubuntu2204)及图形桌面xfce4

    什么是 WSL ? 在计算机上使用 Linux 系统通常有两种方式:使用虚拟机或安装 Linux 系统。使用虚拟机时开销较大,直接使用 Linux 系统虽然可以带来流畅体验,但与 Windows 之间来回切换比较麻烦。为此,微软开发了适用于 Linux 的 Windows 子系统,简称 WSL。 WSL 可以让开发人员可以

    2024年02月13日
    浏览(48)
  • 【2023最新版】Windows11家庭版:安卓子系统(WSA)安装及使用教程【全网最详细】

    目录  一、准备工作 1. 检查虚拟化功能 2. 找到“Wndows功能” 3. 启用Hyper-V和虚拟机平台 4. 家庭版安装Hyper-V(若步骤3找不到Hyper-V) 二、安装安卓子系统 1. 进入开发者选项 2. 下载Windows Subsystem for Android™ with Amazon Appstore 3. 若步骤2无法下载 4. 安装 三、使用adb命令连接安卓子

    2024年02月12日
    浏览(51)
  • Windows 使用 Linux 子系统,轻轻松松安装多个linux

    简称WSL,是一个在Windows 1011上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层。它是由微软与Canonical公司合作开发,其目标是使纯正的Ubuntu、Debian等映像能下载和解压到用户的本地计算机,并且映像内的工具和实用工具能在此子系统上原生运行。如果使用Windows10 2004以

    2024年02月14日
    浏览(46)
  • 【嵌入式Linux学习笔记】platform设备驱动和input子系统

    对于Linux这种庞大的操作系统,代码重用性非常重要,所以需要有相关的机制来提升效率,去除重复无意义的代码,尤其是对于驱动程序,所以就有了platform和INPUT子系统这两种工作机制。 学习视频地址:【正点原子】STM32MP157开发板 platform 驱动框架分为总线、设备和驱动。总

    2024年02月07日
    浏览(57)
  • Linux GPIO 和 Pinctrl 子系统的使用(十四)

     个人名片: 🦁作者简介:学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755@qq.com 🦉个人WeChat:Vir2021GKBS 🐼 本文由妄北y原创,首发CSDN 🎊🎊🎊 🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。 专栏导航: 妄北y系列专栏导航: C/C++的基

    2024年04月26日
    浏览(35)
  • Windows 10 安装安卓子系统 WSA(Magisk/KernelSU)使用 WSA 工具箱安装 APK

    from https://blog.zhjh.top/archives/XokySA7Rc1pkVvnxAEP5E 前提是系统为 Windows 10 22H2 10.0.19045.2311 或更高版本,尽量新。 在 Microsoft Store 中安装 Ubuntu LTS(或你喜欢的发行版),此处为 Ubuntu 20.04.5 LTS。 开始菜单找到并打开安装的 Ubuntu,执行以下命令。Linux 子系统的 /mnt 目录,可以访问到

    2024年02月06日
    浏览(133)
  • Windows 自带的 Linux 子系统(WSL)安装与使用

    WSL官网安装教程: https://learn.microsoft.com/zh-cn/windows/wsl/install Windows 自带的Linux子系统,比用VM什么的香太多了。可以自己看官方教程,也可以以下步骤完成。 如果中间遇到我没遇到的问题百度,可以在评论区评论,或者自己百度其他教程解决。 搜索powerShell, 然后右键以管理

    2024年02月21日
    浏览(49)
  • Windows 下使用 WSL 安装 Kali Linux 子系统

    使用 WSL 安装 Kali,方便快捷使用。 类似于 Cygwin 不过听说通过 WSL 安装的做了隔离,不能够访问硬件,也就无法进行硬件相关的渗透,具体有待核实。 环境:Windows 10 21H2 教育版 参考:

    2024年02月04日
    浏览(50)
  • 开始在适用于 Linux 的 Windows 子系统上使用 Git

    Git 是最常用的版本控制系统。 使用 Git,可以跟踪对文件所做的更改,以便记录已完成的操作,并能够在需要时还原到文件的早期版本。 Git 还可以简化协作,使多个人员所做的更改全部合并到一个源中。 一个重要的注意事项:启用 WSL 并安装 Linux 发行版时,将安装与计算机

    2024年02月11日
    浏览(46)
  • Win10安卓子系统安装教程

    win10安卓子系统和win11子系统的安装一样,都必须要安装 适用于 Android ™的 Windows 子系统设置 的软件。 (软件链接: https://pan.baidu.com/s/1rlj9KGdKXKVmuFolNroYfg 提取码: 3p74 ) 下载完成并解压后,找到文件夹中的 Run.bat 程序 点击运行,会出现以下画面 安装成功后会出现 Google Play商店

    2024年01月16日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包