Created
January 12, 2022 23:37
-
-
Save Vincent-Stragier/0583a7ecb5a74f104b36e74d4de933aa to your computer and use it in GitHub Desktop.
Read TP ADC on some Allwinner CPU (A20)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Related to https://forum.armbian.com/topic/15032-reading-a20-lradc0-and-lradc1-values/?do=findComment&comment=134459 | |
To use this script you must remove kernel drivers related to the A20's adc. | |
Note that this script has been developped on a pcDuino 3, running on Armbian | |
(Linux pcduino3 5.15.13-sunxi #trunk.0004 SMP Wed Jan 5 17:53:11 UTC 2022 armv7l GNU/Linux) | |
The values registers have been set using the A20 Allwinner User Manual and by reverse engineering | |
the previous OS images released by the pcDuino 3 teams in order to find a correct configuration. | |
(pcDuino3 OS iso used: pcduino3_dd_sdbootable_20141110) | |
You can list them: | |
$ sudo lsmod | grep adc | |
axp20x_adc 16384 0 | |
sun4i_gpadc_iio 16384 0 | |
industrialio 57344 2 sun4i_gpadc_iio,axp20x_adc | |
sun4i_gpadc 16384 0 | |
And remove them temporaly from the kernel using: | |
$ sudo rmmod sun4i_gpadc_iio | |
$ sudo rmmod sun4i_gpadc | |
Blacklisting the modules would remove the need of runing the rmmod. | |
WARNING: Note that removing the access to this ADC is probably removing the | |
access to the temperature sensor. | |
""" | |
from time import sleep | |
import os | |
import mmap | |
# Activate or deactivate the debug print | |
DEBUG = False | |
# Check CPU | |
a20 = "sun7i" in open("/proc/cpuinfo", "r").read() | |
if not a20: | |
exit("This program only works on Allwinner sun7i (A20) cpus.") | |
# TP register base address = 0x01C25000 (cfr. A20 manual page 205) | |
TP_BASE_ADDRESS = 0x01C25000 | |
# TP Control Register0 | |
TP_CTRL0 = TP_BASE_ADDRESS + 0x00 | |
# TP Control Register1 | |
TP_CTRL1 = TP_BASE_ADDRESS + 0x04 | |
# TP Pressure Measurement and touch sensitive Control Register | |
TP_CTRL2 = TP_BASE_ADDRESS + 0x08 | |
# Median and averaging filter Controller Register | |
TP_CTRL3 = TP_BASE_ADDRESS + 0x0c | |
# TP Interrupt FIFO Control Reg | |
TP_INT_FIFOC = TP_BASE_ADDRESS + 0x10 | |
# TP Interrupt FIFO Status Register | |
TP_INT_FIFOS = TP_BASE_ADDRESS + 0x14 | |
# TP Temperature Period Register | |
TP_TPR = TP_BASE_ADDRESS + 0x18 | |
# TP Common Data | |
TP_CDAT = TP_BASE_ADDRESS + 0x1c | |
# Temperature Data Register | |
TEMP_DATA = TP_BASE_ADDRESS + 0x20 | |
# TP Data Register | |
TP_DATA = TP_BASE_ADDRESS + 0x24 | |
# TP IO Configuration | |
TP_IO_CONFIG = TP_BASE_ADDRESS + 0x28 | |
# TP IO Port Data | |
TP_PORT_DATA = TP_BASE_ADDRESS + 0x2c | |
MAP_MASK = mmap.PAGESIZE - 1 | |
def write(file, address, content: bytearray): | |
"""Write the content at a specific address and check success. | |
Return the success of the operation. | |
""" | |
file.seek(address) | |
file.write(content) | |
file.seek(address) | |
return file.read(len(content)) == content | |
def print_all(mem): | |
"""Print the binary value of all the registers related to the ADC.""" | |
mem.seek(TP_CTRL0 & MAP_MASK) | |
print("TP_CTRL0:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_CTRL1 & MAP_MASK) | |
print("TP_CTRL1:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_CTRL2 & MAP_MASK) | |
print("TP_CTRL2:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_CTRL3 & MAP_MASK) | |
print("TP_CTRL3:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_INT_FIFOC & MAP_MASK) | |
print("TP_INT_FIFOC", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_INT_FIFOS & MAP_MASK) | |
print("TP_INT_FIFOS", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_TPR & MAP_MASK) | |
print("TP_TPR :", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_CDAT & MAP_MASK) | |
print("TP_CDAT:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TEMP_DATA & MAP_MASK) | |
print("TEMP_DATA:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_DATA & MAP_MASK) | |
print("TP_DATA:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_IO_CONFIG & MAP_MASK) | |
print("TP_IO_CONFIG:", [bin(d) for d in list(mem.read(4))]) | |
mem.seek(TP_PORT_DATA & MAP_MASK) | |
print("TP_PORT_DATA:", [bin(d) for d in list(mem.read(4))]) | |
try: | |
mem = mmap.mmap(os.open("/dev/mem", os.O_RDWR | os.O_SYNC), mmap.PAGESIZE, | |
mmap.MAP_SHARED, offset=TP_BASE_ADDRESS & ~MAP_MASK) | |
if DEBUG: | |
print("Register initial") | |
print_all(mem) | |
# The correct way to do it is to add the 4 bytes together as follow : | |
REGISTERS_CONFIG0 = bytearray(( | |
# Bit 15:0 TACQ. | |
# Touch panel ADC acquire time CLK_IN/(16*(N+1)) | |
0b00111111, | |
0b00000000, | |
# Bit 23 ADC_FIRST_DLY_MODE. | |
# ADC First Convert Delay Mode Select: | |
# 0: CLK_IN/16, and 1: CLK_IN/16*256 | |
# Bit 22 ADC_CLK_SELECT. | |
# ADC Clock Source Select: | |
# 0: HOSC(24MHZ), and 1: Audio PLL | |
# Bit 21:20 ADC_CLK_DIVIDER. | |
# ADC Clock Divider(CLK_IN) | |
# 00: CLK/2, 01: CLK/3, 10: CLK/6, and 11: CLK/1 | |
# Bit 19:16 FS_DIV. | |
# ADC Sample Frequency Divider | |
# xxxx: CLK_IN/2^(20-xxxx) | |
0b00111111, | |
# Bit 31:24 ADC_FIRST_DLY. | |
# ADC First Convert Delay Time(T_FCDT)setting | |
# Based on ADC First Convert Delay Mode select (Bit 23) | |
# T_FCDT = ADC_FIRST_DLY * ADC_FIRST_DLY_MODE | |
0b0 | |
) | |
) | |
write(mem, TP_CTRL0 & MAP_MASK, REGISTERS_CONFIG0) | |
REGISTERS_CONFIG1 = bytearray(( | |
# Bit 7 CHOP_TEMP_EN | |
# Chop temperature calibration enable | |
# 0: Disable, and 1: Enable | |
# Bit 6 TOUCH_PAN_CALI_EN. | |
# Touch Panel Calibration | |
# 1: start Calibration, it is clear to 0 after calibration | |
# Bit 5 TP_DUAL_EN. | |
# Touch Panel Double Point Enable | |
# 0: Disable, and 1: Enable | |
# Bit 4 TP_MODE_EN. | |
# Tp Mode Function Enable | |
# 0: Disable, and 1: Enable | |
# Bit 3 TP_ADC_SELECT. | |
# Touch Panel and ADC Select: | |
# 0: TP, and 1: ADC | |
# Bit 2:0 ADC_CHAN_SELECT. | |
# Analog input channel Select In Normal mode: | |
# 000: X1 channel, 001: X2 Channel | |
# 010: Y1 Channel, 011: Y2 Channel | |
# 1xx : 4-channel robin-round | |
# FIFO Access Mode, based on this setting | |
# Selecting one channel, FIFO will access that channel data; | |
# Selecting four channels FIFO will access each channel data | |
# in successive turn, first is X1 data. | |
0b00011000, | |
# Bit 19(15):12 STYLUS_UP_DEBOUNCE. | |
# See under | |
# Bit 11:10 | |
# Bit 9 STYLUS_UP_DEBOUCE_EN. | |
# Stylus Up De-bounce Function Select | |
# 0: Disable, and 1: Enable | |
# Bit 8 / | |
0b00000000, | |
# Bit 23:20 / | |
# Bit 19:(16)12 STYLUS_UP_DEBOUNCE. | |
# Stylus Up De-bounce Time setting | |
# 0x00: 0 | |
# …. | |
# 0xff: 2N*(CLK_IN/16*256) | |
0b00000000, | |
# Bit 31:24 / | |
0b00000000 | |
) | |
) | |
write(mem, TP_CTRL1 & MAP_MASK, REGISTERS_CONFIG1) | |
REGISTERS_CONFIG2 = bytearray(( | |
0b00000000, | |
0b00000000, | |
0b00000000, | |
0b11110100 | |
) | |
) | |
write(mem, TP_CTRL2 & MAP_MASK, REGISTERS_CONFIG2) | |
REGISTERS_CONFIG3 = bytearray(( | |
# Bit 31(7):3 / | |
# Bit 2 FILTER_EN. | |
# Filter Enable | |
# 0: Disable, and 1: Enable | |
# Bit 1:0 FILTER_TYPE. | |
# Filter Type: | |
# 00: 4/2, 01: 5/3, 10: 8/4, and 11: 16/8 | |
0b00000101, | |
# Bit 31:(8)3 / | |
0b00000000, | |
0b00000000, | |
0b00000000 | |
) | |
) | |
write(mem, TP_CTRL3 & MAP_MASK, REGISTERS_CONFIG3) | |
REGISTERS_CONFIGFIFOC = bytearray(( | |
0b00000000, | |
0b00000000, | |
0b00000001, | |
0b00000000 | |
) | |
) | |
write(mem, TP_INT_FIFOC & MAP_MASK, REGISTERS_CONFIGFIFOC) | |
REGISTERS_TPR = bytearray(( | |
0b00000000, | |
0b00000000, | |
0b00000000, | |
0b00000000 | |
) | |
) | |
write(mem, TP_TPR & MAP_MASK, REGISTERS_TPR) | |
REGISTERS_CONFIGIO = bytearray(( | |
# Bit 7 / | |
# Bit 6:4 TX_N_SELECT | |
# TX_N Port Function Select: | |
# 000:Input, 001:Output, and 010: TP_XN | |
# Bit 3 / | |
# Bit 2:0 TX_P_SELECT | |
# TX_P Port Function Select: | |
# 000:Input, 001:Output, and 010: TP_XP | |
0b00100010, | |
# 0b00000000, # All inputs | |
# 0b00010001, # All inputs | |
# Bit 14:12 TY_N_SELECT | |
# TY_N Port Function Select: | |
# 000:Input, 001:Output, and 010: TP_YN | |
# Bit 11 / | |
# Bit 10:8 TY_P_SELECT | |
# TY_P Port Function Select: | |
# 000:Input, 001:Output, and 010: TP_YP | |
0b00100010, | |
# 0b00000000, # All inputs | |
# 0b00010001, # All inputs | |
# Bit 31:15 / | |
0b00000000, | |
0b00000000 | |
) | |
) | |
write(mem, TP_IO_CONFIG & MAP_MASK, REGISTERS_CONFIGIO) | |
if DEBUG: | |
print("Final config") | |
print_all(mem) | |
while True: | |
for adc in (0, 1, 2, 3): | |
if DEBUG: | |
print(f"\nA{adc+2} {45 * '='}") | |
print_all(mem) | |
print() | |
# Select the ADC | |
write(mem, TP_CTRL1 & MAP_MASK, bytearray( | |
(0b00011000 | adc, 0b00000000, 0b00000000, 0b00000000))) | |
# LED some times to the ADC logic to set the value | |
sleep(0.01) | |
mem.seek(TP_DATA & MAP_MASK) | |
LSB, MSB = mem.read(2) | |
print(f"A{adc+2}: {(MSB << 8) + LSB}", sep=" ") | |
print() | |
finally: | |
mem.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment