Cover image for Linux ADC

Linux ADC


Timeline

Timeline

2026-01-14

init

This article introduces the basics of Linux ADC drivers, discusses the difference between analog and digital signals as well as the fundamental concepts of Analog-to-Digital Converters (ADC), and summarizes the working principles of ADC in the processes of sampling, holding, quantization, and encoding, along with its application scenarios and key parameters such as resolution.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. gpio Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

ADC Basics

Analog Signal and Digital Signal

Analog SignalIt is a continuously varying signal whose value can take any value within a certain range. It is continuous in both time and amplitude, and can accurately reflect changes in physical quantities in nature. For example, the voltage signal output by a temperature sensor, the sound signal picked up by a microphone, and the light intensity signal detected by a photoresistor all fall into the category of analog signals.

Digital SignalIt is a discrete signal whose value can only take specific values within a limited range, usually expressed in binary form. Its characteristics are that it is discrete in time (with fixed time intervals between sampling points) and discrete in amplitude (quantized values). For example, data stored and processed in computers, audio data in MP3 files, and pixel values in JPEG image files are all concrete manifestations of digital signals.

DimensionCharacteristics of Analog SignalCharacteristics of Digital Signals
EssenceContinuous variation, arbitrary valuesDiscrete values, often represented in binary
CharacteristicsBoth time and amplitude are continuousBoth time (sampling interval) and amplitude (quantized value) are discrete
AdvantagesAccurately reflects changes in natural physical quantitiesEasy to store/transmit/process, strong anti-interference capability
Typical ScenariosRaw sensor signals, natural acoustic/optical/electrical signalsComputer data, digital audio/video files

ADC Concept

ADC isAnalog-to-Digital Converterabbreviation, can be translated asAnalog-to-Digital Converter. It is an electronic device or circuit module used toconvert continuously varying analog signals (such as voltage, current, etc.) into discrete digital signals. These digital signals can be processed and stored by computers, microcontrollers, or other digital systems.

The conversion of analog signals to digital signals by an ADC is typically divided intosamplingholdingquantizationandencodingfour steps. The definition and function of each step are shown in the following table:

StepDefinitionFunctionKey Parameters or Features
SamplingThe process of measuring an analog signal at fixed time intervals.Discretizes a time-continuous analog signal, laying the foundation for subsequent digital processing.Sampling Rate, i.e., the number of samples per second. According to the Nyquist sampling theorem, the sampling rate must be at least twice the highest frequency of the signal.
HoldingAfter sampling, the instantaneous value of the signal is held for a period of time for subsequent processing.Ensures the signal does not distort due to rapid changes during quantization, improving conversion accuracy and stability.No specific parameters; the main purpose is to stabilize the signal.
QuantizationThe process of mapping the continuous amplitude values obtained from sampling to a set of discrete values.Discretizes a signal that is continuous in amplitude, enabling it to be represented by a digital system.Resolution, typically expressed in bits (e.g., 8-bit, 10-bit, 12-bit, etc.). Higher resolution results in more accurate signal representation.
EncodingThe process of converting quantized values into binary code.Represent discrete values in a form recognizable by digital systems, facilitating storage, transmission, and processing, so that analog signals can be used by computers or other digital devices.No specific parameters; the focus is on generating binary code suitable for use by digital systems.

ADCs are widely used in sensor signal acquisition, audio processing, image processing, industrial automation, and medical equipment:

  1. Sensor signal acquisition: Many sensors output analog signals. For example, environmental data collected by temperature sensors, pressure sensors, and photosensitive sensors typically exist in analog form.
  2. Audio processing: Sound signals picked up by microphones are analog signals.
  3. Image processing: Light signals captured by cameras are essentially analog signals.
  4. Industrial automation: Measurement of various physical quantities (such as speed, position, pressure, etc.) typically relies on analog signals output by sensors.
  5. Medical equipment: Many biological signals (such as ECG, EEG, etc.) are acquired in analog form.

ADC Resolution

The resolution of an ADC refers to its ability to distinguish the smallest change in signal. In other words, it is the fineness with which the ADC quantizes an analog signal into discrete digital values. In an ADC, higher resolution means it can represent the input analog signal more accurately. When we convert a digital signal back to analog through a DAC (digital-to-analog converter), a high-resolution ADC can produce results closer to the original analog signal.

ADC resolution is usually expressed in bits, such as 8-bit, 10-bit, 12-bit, etc. The number of bits determines the number of discrete values the ADC can output; an n-bit ADC can represent 2^n different values. For example, an 8-bit ADC can represent 2^8=256 different values; a 10-bit ADC can represent 2^10=1024 different values; a 12-bit ADC can represent 2^12=4096 different values. The higher the number of bits, the higher the resolution, and the smaller the signal change that can be distinguished.

Resolution can be calculated using the following formula:

分辨率=量程2n\text{分辨率} = \dfrac{\text{量程}}{2^n}

Where:

  1. The range refers to the maximum range of the ADC input signal (i.e., the reference voltage range);
  2. n is the number of bits of the ADC;
  3. 2^n represents the total number of discrete values that an ADC can distinguish.

Example: Assume a 3-bit ADC has a reference voltage (range) of 2V, calculate its resolution:

  1. Range = 2V
  2. Number of bits is 3, 2^n = 2^3 = 8;

Substituting into the resolution formula gives 2V/8 = 0.25V. This means the ADC can divide the input signal into 8 discrete levels, with a step size of 0.25V between each level.

Besides resolution, other parameters of the ADC are as follows

ParameterDefinitionFunction or Significance
Sampling TimeThe time required to complete one sampling.Determines how quickly the ADC can capture changes in the input signal. If the sampling time is too long, it may cause signal distortion or loss of detail.
Conversion TimeThe time required to convert an analog signal to a digital signal.Directly affects the speed and efficiency of the ADC. A shorter conversion time means the ADC can process signals faster.
Sampling FrequencyThe number of samples collected per second, commonly expressed in ksps (thousands of samples per second) or Msps (millions of samples per second).Determines the ADC’s ability to capture signal changes. According to the Nyquist sampling theorem, the sampling frequency must be at least twice the highest frequency of the signal to accurately reconstruct it.
AccuracyThe error between the digital value output by the ADC and the actual analog input value.Reflects the reliability and measurement quality of the ADC, serving as an important indicator of its performance.

RK3568 ADC

In the peripheral resource block diagram of the RK3568 datasheet, it can be found that the RK3568 SoC has a total of8-channel SARADC

SARADCSuccessive Approximation Register Analog-to-Digital Converter) is called in ChineseSuccessive Approximation Register ADC

In addition to the SAR ADC, there are two TS-ADC (Temperature Sensor ADC) channels, which are used to detect the temperature of the CPU and GPU respectively. If the temperature is detected to be too high over a period of time, an interrupt signal is sent to the processor to take cooling measures; if the temperature remains too high, the entire chip will be reset, achieving a restart effect. The TS-ADC is only responsible for detecting the temperature of the CPU and GPU, so it is not brought out to the baseboard.

ADC Key

The ADC key is a SARADC peripheral. The working principle of the ADC key isto connect multiple keys to the same ADC pin, generating different voltages through different voltage divider resistors. The ADC measures the voltage and compares it with preset voltage ranges to determine which key is pressed.

Key Array
Key Array

Two ADC channels are used in HW_ID and BOM_On the ID, it is used for hardware identification of different electronic devices. However, since Topeet’s peripherals are all universal, these two ADC channels are not used.

HW_ID
HW_ID

BOM_ID
BOM_ID

Four ADC channels are connected to a 6-pin header, as shown in the schematic below.

SARADC
SARADC

Summary as follows

ADC InterfaceMultiplexed Function
SARADC VIN0Used as button pins (Volume +, Volume -, HOME, BACK)
SARADC VIN1Used as HW ID pin
SARADC VIN2Not used
SARADC VIN3Used as BOM ID pin
SARADC VIN4Not used
SARADC VIN5Not used
SARADC VIN6Not used
SARADC VIN7Not used

Successive approximation ADC

Successive Approximation ADC (Successive Approximation Register ADC, abbreviated as SAR ADC) is a common type of analog-to-digital converter whose operating principle is based on the successive approximation algorithm. It determines a digital value close to the input signal by progressively comparing the input analog signal with an internally generated reference voltage. The schematic diagram of the SAR ADC working principle is shown below.

SAR ADC
SAR ADC

From the above diagram, it can be seen that the successive approximation register analog-to-digital converter (SAR ADC) mainly consists of the following parts:

  1. Comparator: Used to compare the input analog signal U1 with the reference voltage U0 output by the D/A converter (DAC) and generate a feedback signal.
  2. N-bit D/A Converter (DAC): Converts the digital value into an analog voltage, which is compared with the input signal to progressively approach the target value.
  3. N-bit Successive Approximation Register (SAR): Used to store the current comparison result and adjust the value based on the comparator’s feedback, ultimately determining the digital output.
  4. Bit-setting Selection Logic: Controls the bit selection process of the successive approximation register, adjusting the DAC output voltage using the binary search method.

Working Process:

  1. First, the analog input signal U1 enters the ADC and is temporarily stored by the sampling circuit for subsequent processing.
  2. Then, the successive approximation register (SAR) is initialized, and under the control of the bit-setting selection logic circuit, the final digital output value is progressively determined using the binary search method.

During the conversion process, the SAR starts by assuming the most significant bit is 1 and passes the current N-bit digital value to the D/A converter. The DAC converts it into an analog voltage U0, which is then sent to the comparator to be compared with the input signal U1. If U0 is less than U1, the current bit remains 1; otherwise, the current bit is changed to 0. This process repeats, determining one bit at a time, until all N bits are completed, forming the final digital output.

  1. Finally, the SAR outputs the determined N-bit digital value, completing one analog-to-digital conversion.

An example is as follows:

A 5-bit successive approximation ADC with a reference voltage of 32mV and a measured voltage of 21.5mV

A 5-bit successive approximation ADC with a reference voltage of 32mV and a measured voltage of 21.5mV
A 5-bit successive approximation ADC with a reference voltage of 32mV and a measured voltage of 21.5mV

  1. Set the most significant bit: The SAR register sets the most significant bit (bit 5) to ‘1’ and the remaining bits to ‘0’, resulting in a current SAR value of 10000 (binary), which is 16 (decimal). The DAC generates a reference voltage U0 = 16 × 1mV = 16 mV based on the SAR value. The comparator compares U1 = 21.5 mV and U0 = 16 mV. Since U1 > U0, the current bit is retained as ‘1’.
  2. Set the next highest bit: The SAR register keeps the most significant bit as ‘1’ and sets the next highest bit (bit 4) to ‘1’, resulting in a SAR value of 11000 (binary), which is 24 (decimal). The DAC generates a reference voltage U0 = 24 × 1 mV = 24 mV. The comparator compares U1 = 21.5 mV and U0 = 24 mV. Since U1 < U0, the current bit is reset to ‘0’.
  3. Set the third bit: The SAR register keeps the first two bits as ‘10’ and sets the third bit (bit 3) to ‘1’, resulting in a SAR value of 10100 (binary), which is 20 (decimal). The DAC generates a reference voltage U0 = 20 × 1 mV = 20 mV. The comparator compares U1 = 21.5 mV and U0 = 20 mV. Since U1 > U0, the current bit is retained as ‘1’.
  4. Set the fourth bit: The SAR register keeps the first three bits as ‘101’ and sets the fourth bit (bit 2) to ‘1’, resulting in a SAR value of 10110 (binary), which is 22 (decimal). The DAC generates a reference voltage U0 = 22 × 1 mV = 22 mV. The comparator compares U1 = 21.5 mV and U0 = 22 mV. Since U1 < U0, the current bit is reset to ‘0’.
  5. Set the least significant bit: The SAR register keeps the first four bits as ‘1010’ and sets the least significant bit (bit 1) to ‘1’, resulting in a SAR value of 10101 (binary), which is 21 (decimal). The DAC generates a reference voltage U0 = 21 × 1 mV = 21 mV. The comparator compares U1 = 21.5 mV and U0 = 21 mV. Since U1 > U0, the current bit is retained as ‘1’.
  6. Result: After successive approximation, the value of the SAR register is 10101 (binary), which is 21 (decimal).

Operate the ADC

Enter the/sys/bus/iio/devices/iio:device0directory

/sys/bus/iio/devices/iio:device0
/sys/bus/iio/devices/iio:device0

It should be noted thatin_voltageX_rawandin_voltage_scale

  • in_voltageX_rawRepresents the raw sampling value of a certain ADC channel, where X is the channel number (e.g., 0, 1, 2, …, 7). It stores the digital code after ADC conversion as an integer. Users can read this file to obtain the unscaled raw data of the corresponding channel. Note that these values have not been scaled by range and must be combined within_voltage_scalefor calculation to obtain the actual voltage value.
  • in_voltage_scaleis used toconvert the ADC raw sampling value to the actual voltage value as a scaling factor, typically defined as 1.8/1024. It is represented as a floating-point number, and the unit can be millivolts (mV) or volts (V), depending on the hardware implementation. By multiplying the raw value ofin_voltageX_rawbyin_voltage_scale, the actual voltage value of the corresponding channel can be obtained, thus achieving the conversion from digital code to physical quantity

Here, ADC channel 7 is used as an example for demonstration. The corresponding hardware is pin 5 of J24 on the baseboard, as shown in the figure below

SARADC
SARADC

The raw sampling value corresponding to ADC channel 7 isin_voltage7_raw, read using the cat command, and the value read is as follows:

1
2
$ cat involtage7_raw
1017

Here, the unscaled raw data is read, and then continue to use the cat command to read the scaling factorin_voltage_scalevalue, as shown below:

1
2
$ cat in_voltage_scale
1.757812500

Then multiply the obtained raw sampling value by the scaling factor to get the actual voltage value: 1017 x 1.757812500 = 1787.6953125 mV = 1.787 V

In addition to the SAR ADC mentioned above, there are also two TSADCs, which are used to query the temperatures of the CPU and GPU respectively,

Check CPU temperature:

1
cat /sys/class/thermal/thermal_zone0/temp

Check GPU temperature:

1
cat /sys/class/thermal/thermal_zone1/temp

ADC Driver

Write an ADC driver that creates an ADC device node via a miscellaneous device, through which the ADC value of the corresponding channel can be read.

API Functions

iio_channel_get()

Function Prototype

1
struct iio_channel *iio_channel_get(const char *name, const char *channel_name);

Core Function

From the Linux IIO subsystem, obtain a correspondingstruct iio_channelchannel structure pointer; all subsequent operations on this ADC channel depend on this pointer.

Parameter Description

  • name: Specifies theIIO device name/IDto operate on; if passingNULLindicates using the system default IIO device; commonly used in SARADC scenariosNULL
  • channel_name: Specifies the ADC channel name to open; the specific name must match the kernel driver.

Return value

  • ✅ Success: returns a validstruct iio_channel *structure pointer (channel handle);
  • ❌ Failure: returns a kernel error pointerERR_PTR(错误码), cannot be used directly as a normal pointer.

iio_channel_release()

Obtained channel handleMust be released after use, otherwise it causes kernel resource leakage. The release function is fixed as:

1
void iio_channel_release(struct iio_channel *channel);
  • Parameter: it is theiio_channel_getobtained channel pointer
  • Feature: no return value, safe to call.

struct iio_channel

1
2
3
4
5
6
struct iio_channel {
struct iio_device *indio_dev; // IIO device bound to the current channel (e.g., SARADC device)
const char *channel_name; // Channel name, consistent with the input parameter channel_name
int channel_index; // Channel index, e.g., VIN0 corresponds to 0, VIN3 corresponds to 3
bool is_output; // Whether it is an output channel; ADC is an 'input channel' and is fixed to false
};

iio_read_channel_raw()

Function Prototype

1
int iio_read_channel_raw(struct iio_channel *channel, int *val);

Core Function

Read from an already opened IIO channel (ADC channel)raw integer data without any calibration or conversion, which is the core function for reading ADC data.

✔️ In your SARADC scenario: the readvalis the voltage collected by the ADC pin corresponding to theraw digital value(for example, a 10-bit ADC gives values from 0 to 1023).

Parameter Description

  • channel: Input parameter,iio_channel_getthe channel handle returned on success,must be a valid pointer
  • val: Output parameter, an integer pointer. After the function executes successfully, the collected ADC raw data will be written to the variable pointed to by this address.

Return Value

  • ✅ Success: returns0
  • ❌ Failure: returnsnegative kernel error codes(e.g., -EINVAL invalid argument, -ENODEV device not found, etc.).

Key Notes

The readvalisraw bare data

  • No unit, no calibration, no conversion; it is just the pure digital value after ADC analog-to-digital conversion;
  • The value range is determined by the ADC bit resolution (e.g., 8-bit ADC: 0~255; 10-bit ADC: 0~1023; 12-bit ADC: 0~4095);
  • If you need to convert to an actual voltage value, you must write your own formula:实际电压 = (原始值 / ADC最大量程) * ADC参考电压

Standard Writing in Kernel Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Step 1: Define variables
struct iio_channel *adc_channel;
int adc_raw_data;
int ret;

// Step 2: Obtain ADC channel handle (example: open VIN0 channel of SARADC)
adc_channel = iio_channel_get(NULL, "vin0");
// Step 3: Check if channel acquisition is successful
if (IS_ERR(adc_channel)) {
ret = PTR_ERR(adc_channel); // Extract error code
printk("获取ADC通道失败: %d\n", ret);
return ret;
}

// Step 4: Read ADC raw data
ret = iio_read_channel_raw(adc_channel, &adc_raw_data);
if (ret < 0) {
printk("读取ADC数据失败: %d\n", ret);
iio_channel_release(adc_channel); // Release resources even on failure!
return ret;
}
printk("ADC原始采集值:%d\n", adc_raw_data);

// Step 5: Must release resources after using the channel
iio_channel_release(adc_channel);

Device tree

Add device node:

1
2
3
4
adc7: adc-test {
compatible = "myadc";
io-channels = <&saradc 7>;
};

The key is io-channels, meaning the 7th channel of the ADC is to be used

Driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/platform_device.h>
#include <linux/iio/consumer.h>
#include <linux/miscdevice.h>
#include <linux/of.h> // Add support for device tree

#define CMD_READ_SCALE _IOR('A', 0x1, int) // Define ioctl commands

static struct iio_channel *adc_chan; // IIO channel pointer

// File operations: ioctl implementation
static long adc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int scale = 0;

switch (cmd) {
case CMD_READ_SCALE:
// Read raw value from ADC channel
if (iio_read_channel_raw(adc_chan, &scale) < 0) {
pr_err("Failed to read ADC channel raw value\n");
return -EIO;
}
// Copy data to user space
if (copy_to_user((int *)arg, &scale, sizeof(scale))) {
pr_err("Failed to copy data to user space\n");
return -EFAULT;
}
break;

default:
pr_err("Invalid ioctl command\n");
return -EINVAL;
}

return 0;
}

// File operations structure
static const struct file_operations adc_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = adc_dev_ioctl,
};

// Miscellaneous device structure
static struct miscdevice adc_dev = {
.minor = MISC_DYNAMIC_MINOR, // Dynamically allocate minor device number
.name = "adc", // Device name
.fops = &adc_dev_fops, // File operation interface
};

// Device tree match table
static const struct of_device_id adc_driver_match[] = {
{ .compatible = "myadc" },
{ }
};
MODULE_DEVICE_TABLE(of, adc_driver_match);

// Platform device probe function
static int adc_driver_probe(struct platform_device *pdev) {
// Get IIO channel
adc_chan = iio_channel_get(&pdev->dev, NULL);
if (IS_ERR(adc_chan)) {
pr_err("Failed to get IIO channel\n");
return PTR_ERR(adc_chan);
}

// Register miscellaneous device
if (misc_register(&adc_dev)) {
pr_err("Failed to register misc device\n");
iio_channel_release(adc_chan); // Release IIO channel
return -ENODEV;
}

pr_info("ADC driver probed successfully\n");
return 0;
}

// Platform device remove function
static int adc_driver_remove(struct platform_device *pdev) {
// Unregister miscellaneous device
misc_deregister(&adc_dev);

// Release IIO channel
iio_channel_release(adc_chan);

pr_info("ADC driver removed successfully\n");
return 0;
}


// Platform device driver structure
static struct platform_driver adc_driver = {
.probe = adc_driver_probe,
.remove = adc_driver_remove,
.driver = {
.name = "myadc", // Driver name
.owner = THIS_MODULE,
.of_match_table = adc_driver_match,
},
};


// Module initialization function
static int __init adc_driver_init(void) {
int ret;

// Register platform device driver
ret = platform_driver_register(&adc_driver);
if (ret) {
pr_err("Failed to register platform driver\n");
return ret;
}

pr_info("ADC driver module loaded\n");
return 0;
}

// Module exit function
static void __exit adc_driver_exit(void) {
// Unregister platform device driver
platform_driver_unregister(&adc_driver);

pr_info("ADC driver module unloaded\n");
}

module_init(adc_driver_init);
module_exit(adc_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

Test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define CMD_READ_SCALE _IOR('A', 1, int)

int main(int argc, char *argv[]) {
int scale = 0;
int fd;
float value = 0.0; // Used to store the calculated actual voltage value

// Open device file
fd = open("/dev/adc", O_RDWR);
if (fd < 0) {
printf("open error\n");
return -1;
}

// Use ioctl to read scale value
if (ioctl(fd, CMD_READ_SCALE, &scale) < 0) {
printf("ioctl error\n");
close(fd);
return -1;
}

value = (1.8 / 1024) * (float)scale;

// Print the read scale value
printf("val is %lf\n", value);

// Close device file
close(fd);
return 0;
}