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

news2024/10/7 4:27:43

一、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获取相关值

四、我自己写的一个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节点

        4.5 通过iio_read_channel_raw函数读iio接口

五、参考文章

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/767804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据脱敏的 3 种常见方案,好用到爆!

1.SQL数据脱敏实现 MYSQL(电话号码,身份证)数据脱敏的实现 -- CONCAT()、LEFT()和RIGHT()字符串函数组合使用&#xff0c;请看下面具体实现-- CONCAT(str1,str2,…)&#xff1a;返回结果为连接参数产生的字符串 -- LEFT(str,len)&#xff1a;返回从字符串str 开始的len 最左字…

力扣 -- 123. 买卖股票的最佳时机 III

题目链接&#xff1a;123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 参考代码&#xff1a; class Solution { public:int maxProfit(vect…

OKCC呼叫中心的坐席监控功能有什么

最近很多客户都在跟我谈他们企业的电话客服工作量都非常大&#xff0c;虽然客服人员在服务时应该态度谦和&#xff0c;但是遇到难缠的客户&#xff0c;客服人员总有脾气忍不住的时候&#xff0c;言语上会带有情绪&#xff0c;这些客服人员会因为服务水平欠佳让客户不满意从而产…

【Python】json 格式转换 ( json 模块使用 | 列表转 json | json 转列表 | 字典转 json | json 转字典 )

文章目录 一、json 格式转换1、json 模块使用2、代码示例分析 - 列表转 json3、代码示例分析 - 字典转 json json 格式 字符串 与 Python 中的 字典 dict 和 列表 list 变量 可以无缝转换 ; 调用 json.dumps 函数 可以将 Python 列表 / 字典 转为 json ;调用 json.loads 函数 …

基于Vue + 数据组装 + axios请求接口 实现图书信息提交功能实战

前言 上节回顾 上一节针对图书信息录入前的准备做了讲解&#xff0c;主要是使用ElementUI的el-form做录入界面基本布局&#xff0c;包含了el-input el-select el-date-picker 还有图片上传等组件。同时在布局完成后&#xff0c;配合使用el-form自带的rules进行数据提交前验证…

生鲜小程序怎么做

生鲜超市小程序功能介绍 1、产品展示&#xff1a;果蔬生鲜的图片展示&#xff0c;食欲满满的食材图&#xff0c;丰富的生鲜品类&#xff0c;细致的产品描述&#xff0c;提高用户的购买欲望和复购率。 2、在线支付&#xff1a;用户选择好所需食材在线下单&#xff0c;利用第三…

[Linux] 守护进程介绍、服务器的部署、日志文件...

守护进程 我们使用的系统中, 一般以服务器的方式工作 对外提供服务的服务器, 都是以守护进程的方式在系统中工作的. 比如, 我们使用Linux服务器时, 大多都会使用一些终端软件通过ssh远程连接服务器使用. 这就是因为, Linux服务器中 通常默认运行着 ssh服务器的守护进程: 守护…

利用数据分析告警机制,实现鸿鹄与飞书双向集成

需求描述 实现鸿鹄与飞书的双向集成&#xff0c;依赖鸿鹄的告警机制&#xff0c;可以发送用户关心的信息到飞书。同时依赖飞书强大的卡片消息功能&#xff0c;在飞书消息里面能够通过链接&#xff08;如下图&#xff09;返回到鸿鹄以方便用户进一步排查和分析问题。 解决方案 1…

Goby 漏洞发布|致远 M1 移动端 userTokenService 代码执行漏洞

漏洞名称&#xff1a;致远 M1 移动端 userTokenService 代码执行漏洞 English Name&#xff1a;seeyon M1 Server userTokenService Code Execution Vulnerability CVSS core: 10.0 影响资产数&#xff1a;7050 漏洞描述&#xff1a; 致远 M1 Server是一个移动服务。致远 …

7zip修复右击菜单的问题

以管理员权限运行7-Zip File Manager后 &#xff0c;要在右键菜单显示 图标&#xff0c;不然就有问题

苍穹外卖day03——菜品管理业务代码开发

目录 公共字段自动填充——问题分析和实现思路 公共字段自动填充——代码实现(1) 公共字段自动填充——代码实现完善(2) 新增菜品——需求分析与设计 产品原型 ​编辑 接口设计 ​编辑 数据库设计 新增菜品——代码开发1(文件上传接口) 配置文件 Controller层代码 前后…

项目名称:智能家居边缘网关项目

一&#xff0c;项目介绍 软件环境: C语言 硬件环境: STM32G030C8TX单片机开发板 开发工具: Linux平台GCC交叉编译环境以及ukeil (1)边缘网关概念 边缘网关是部署在网络边缘侧的网关&#xff0c;通过网络联接、协议转换等功能联接物理和数字世界&#xff0c;提供轻量化的联接管…

大语言模型的预训练[2]:GPT、GPT2、GPT3、GPT3.5、GPT4相关理论知识和模型实现、模型应用以及各个版本之间的区别详解

大语言模型的预训练[2]:GPT、GPT2、GPT3、GPT3.5、GPT4相关理论知识和模型实现、模型应用以及各个版本之间的区别详解 1.GPT 模型 1.1 GPT 模型简介 在自然语言处理问题中&#xff0c;可从互联网上下载大量无标注数据&#xff0c;而针对具体问题的有标注数据却非常少&#x…

原型设计用什么比较好?这4款值得体验

无论是UI还是UX设计师&#xff0c;工作中肯定少不了交互设计。在设计原型图时&#xff0c;一款好用的原型设计工具肯定是必不可少&#xff0c;本文就整理了4款为大家推荐&#xff0c;一起来看看吧 即时设计 即时设计是一款更适合国内UI或UX设计师和产品经理使用的原型设计工具…

如何使用Python三方库CCXT

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

<数据结构>NO11.归并排序|递归|非递归|优化

文章目录 归并排序递归写法非递归写法修正方案1.归并一段拷贝一段修正方案2.修正区间 算法优化算法分析 归并排序的应用外排序和内排序 归并排序 递归写法 思路: 如果给出两个有序数组&#xff0c;我们很容易可以将它们合并为一个有序数组。因此当给出一个无序数组时&#xf…

如何解决循环引用的问题

本文已收录于专栏 《Java》 目录 概念说明发现问题解决问题分析问题具体解决注解说明代码实现效果展示 总结提升 概念说明 循环引用是指在对象之间存在相互引用的情况。具体来说&#xff0c;当一个对象A引用了另一个对象B&#xff0c;而对象B又引用了对象A&#xff0c;它们之间…

软件研发开发人员成本计算器

写了个简单的人员工资计算器&#xff0c;用最简单的人天数计算研发投入&#xff0c;其他费用计算稍后补充完善 软件研发成本计算 ——高级工程师中级工程师初级工程师平均日工资项目阶段高级工程师人天中级工程师人天初级工程师人天调研方案产品设计软件开发测试部署培训试运…

操作系统(王道)- 初识操作系统

一、什么是操作系统&#xff1f; 操作系统可以这么理解 操作系统的定义&#xff1a; 操作系统是整个计算机的硬件和软件的管理者&#xff01;&#xff01;&#xff01; 二、操作系统的功能和目标 作为计算机硬件和软件的管理者&#xff0c;操作系统做了什么&#xff1f; 操作系…

安达发|工业系统APS软件与MES软件有哪些区别?

MES 和 APS 有什么区别&#xff1f;MES 是一个制造执行系统&#xff0c;APS 是一个高级计划排程系统系统&#xff0c;两者是互补的关系&#xff0c;APS 和 MES 可以实现计划和车间执行的闭环管理模式。MES 和 APS 有什么区别&#xff1f; mes和aps的区别: MES 是智能化工厂…