Skip to content

Instantly share code, notes, and snippets.

@Vincent-Stragier
Created January 12, 2022 23:37
Show Gist options
  • Save Vincent-Stragier/0583a7ecb5a74f104b36e74d4de933aa to your computer and use it in GitHub Desktop.
Save Vincent-Stragier/0583a7ecb5a74f104b36e74d4de933aa to your computer and use it in GitHub Desktop.
Read TP ADC on some Allwinner CPU (A20)
#!/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