BBK - pwmchip inconsistencies

Hello everyone,

I have come across a problem on the BBK (Debian 8.2 , 2015-09-11 release) and would be very grateful for a bit of an explanation.

I need to work with the PWMs and therefore I have developed a little ugly script in order to work with config-pin.sh (https://github.com/cdsteinkuehler/beaglebone-universal-io) and then export the pwmchip. Down the line I’d like to to have the script running at start-up, prior to launching a executable.

The cape-universaln overlay is applied. The script goes as follow :

`
#!/bin/bash

echo “Configuring pins”

sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P9.22 pwm’
sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P9.21 pwm’
sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P9.14 pwm’
sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P9.16 pwm’
sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P8.19 pwm’
sudo sh -c ‘/home/debian/beaglebone-universal-io/config-pin P8.13 pwm’

echo “Export pins”
if sudo sh -c ‘echo “0” > /sys/class/pwm/pwmchip0/export’ ; then
echo “pwmchip0:pwm0 exported”
else echo “pwmchip0:pwm0 export failed”
fi

if sudo sh -c ‘echo “1” > /sys/class/pwm/pwmchip0/export’ ; then
echo “pwmchip0:pwm1 exported”
else echo “pwmchip0:pwm1 export failed”
fi

if sudo sh -c ‘echo “0” > /sys/class/pwm/pwmchip1/export’; then
echo “pwmchip1:pwm0 exported”
else echo “pwmchip1:pwm0 export failed”
fi

if sudo sh -c ‘echo “1” > /sys/class/pwm/pwmchip1/export’ ; then
echo “pwmchip1:pwm1 exported”
else echo “pwmchip1:pwm1 export failed”
fi

if sudo sh -c ‘echo “0” > /sys/class/pwm/pwmchip2/export’; then
echo “pwmchip2:pwm0 exported”
else echo “pwmchip2:pwm0 export failed”
fi

if sudo sh -c ‘echo “1” > /sys/class/pwm/pwmchip2/export’; then
echo “pwmchip2:pwm1 exported”
else echo “pwmchip2:pwm1 export failed”
fi

if sudo sh -c ‘echo “0” > /sys/class/pwm/pwmchip4/export’; then
echo “pwmchip4:pwm0 exported”
else echo “pwmchip4:pwm0 export failed”
fi

if sudo sh -c ‘echo “1” > /sys/class/pwm/pwmchip4/export’; then
echo “pwmchip4:pwm1 exported”
else echo “pwmchip4:pwm1 export failed”
fi

if sudo sh -c ‘echo “0” > /sys/class/pwm/pwmchip6/export’; then
echo “pwmchip6:pwm0 exported”
else echo “pwmchip6:pwm0 export failed”
fi

if sudo sh -c ‘echo “1” > /sys/class/pwm/pwmchip6/export’; then
echo “pwmchip6:pwm1 exported”
else echo “pwmchip6:pwm1 export failed”
fi

`

I was happily drinking my 10th coffee when i noticed the following:

First boot:

`
debian@arm:~$ cd /sys/class/pwm/
debian@arm:/sys/class/pwm$ ls
pwmchip0 pwmchip2 pwmchip4 pwmchip5 pwmchip6

`

Second boot:

`
debian@arm:~$ cd /sys/class/pwm/
debian@arm:/sys/class/pwm$ ls
pwmchip0 pwmchip1 pwmchip2 pwmchip4 pwmchip6

`

Firstly the result of my little script gives me a total of 7 successfully exported pwms. Shouldn’t there be 8 ???

Secondly the numbering of the pwmchips is not consistent, and it is going to cause some issues when I develop the following C++ part.

Any idea ?

Thanks in advance.

Hi!

I don’t know much about the sysfs PWM capabilities. But I know a bit about the AM335x CPU. It has three PWMSS subsystems, each has a PWM module, each has two outputs (A and B). So there’re six PWM outputs in total. I guess this is what sysfs supports.

Additionaly you can generate PWM pulse trains by the eCAP modules (also integrated in each PWMSS). AFAIK this isn’t supported by sysfs (but libpruio can handle that). Only two eCAP output pins are available on the BBB headers (P9_28 and P9_42). So you can have a maximum of eight PWM outputs at a time.

BR

Heya plc66,

Have you seen this yet ? https://briancode.wordpress.com/2015/01/06/working-with-pwm-on-a-beaglebone-black/

Unfortunately I have yet to use PWMs on the BBB, so can not offer you any first hand experience advice.

Hi William , TJF,

Digging into the directories, I found a solution.

WARNING : It’s only for kernel 4.1.6 with the newest debian 8.2 image from the mighty Robert Nelson. The device tree has been a bit modified according to https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-pwm

It’s a bit ugly but it works. With reference to that page http://klaus.ede.hih.au.dk/index.php/BBB_Enabling_PWM :

Looking for address 48300000 in the memory map, you’ll find this is in the L4_PER block, and this is listed as the “PWM subsystem 0″, and 48300200 is the EHR0 PWM channel and 48300100 the eCAP0 PWM. Also, address 4830200 is the “PWM subsystem 1″, and address 4830400 is the “PWM subsystem 2″.

If I list /sys/devices/platform/ocp/subsystem/devices, I get a list of symbolic links :

`
debian@arm:~$ ls /sys/devices/platform/ocp/subsystem/devices/
40300000.ocmcram 48200000.interrupt-controller ocp:P8_10_pinmux
44d00000.wkup_m3 48300000.epwmss ocp:P8_11_pinmux
44e00000.prcm 48300100.ecap ocp:P8_12_pinmux
44e07000.gpio 48300180.eqep ocp:P8_13_pinmux
44e09000.serial 48300200.ehrpwm ocp:P8_14_pinmux
44e0b000.i2c 48302000.epwmss ocp:P8_15_pinmux
44e10000.scm 48302180.eqep ocp:P8_16_pinmux
44e10000.scm_conf 48302200.ehrpwm ocp:P8_17_pinmux
44e10620.control 48304000.epwmss ocp:P8_18_pinmux
44e10650.cpsw-phy-sel 48304100.ecap ocp:P8_19_pinmux
44e10800.pinmux 48304180.eqep ocp:P8_26_pinmux
44e11324.wkup_m3_ipc 48304200.ehrpwm ocp:P9_11_pinmux
44e35000.wdt 4830e000.lcdc ocp:P9_12_pinmux
44e3e000.rtc 48310000.rng ocp:P9_13_pinmux
47400000.dma-controller 49000000.edma ocp:P9_14_pinmux
47400000.usb 4a100000.ethernet ocp:P9_15_pinmux
47401300.usb-phy 4a101000.mdio ocp:P9_16_pinmux
47401400.usb 4a300000.pruss ocp:P9_17_pinmux
47401b00.usb-phy 4a334000.pru0 ocp:P9_18_pinmux
47401c00.usb 4a338000.pru1 ocp:P9_21_pinmux
48022000.serial 4c000000.emif ocp:P9_22_pinmux
48024000.serial 53100000.sham ocp:P9_23_pinmux
4802a000.i2c 53500000.aes ocp:P9_24_pinmux
48030000.spi 56000000.sgx ocp:P9_26_pinmux
48038000.mcasp alarmtimer ocp:P9_27_pinmux
48042000.timer bone_capemgr ocp:P9_30_pinmux
48044000.timer clk_mcasp0 ocp:P9_42_pinmux
48046000.timer clk_mcasp0_fixed ocp:P9_91_pinmux
48048000.timer cpufreq-voltdm.0 omap-pcm-audio
4804a000.timer edma-dma-engine.0 oprofile-perf.0
4804c000.gpio fixedregulator@0 pm33xx.0
48060000.mmc hdmi pmu
480c8000.mailbox hdmi_audio@0 reg-dummy
480ca000.spinlock leds serial8250
4819c000.i2c musb-hdrc.0.auto snd-soc-dummy
481a0000.spi musb-hdrc.1.auto soc
481a8000.serial ocp soc:mpu
481ac000.gpio ocp:cape-universal sound
481ae000.gpio ocp:l4_wkup@44c00000 tps65217-bl
481cc000.can ocp:P8_07_pinmux tps65217-pmic
481d0000.can ocp:P8_08_pinmux
481d8000.mmc ocp:P8_09_pinmux

`

I can from there list the directory according to the memory address from the Sitara SRM. For instance 48300200.ehrpwm.

`
debian@arm:/sys/devices/platform/ocp/subsystem/devices/48300200.ehrpwm/pwm$ ls
pwmchip0

`

From there on , I can determine the pwmchip number attributed at boot time.

As TJF mentioned, there are two channels per EHRPWM, and it’s a matter of echo 0 or 1 to …/pwmchip0/export as before (o for channel A , 1 for channel B).

Programmatically, I ended up modifying the library from https://github.com/SaadAhmad/beaglebone-black-cpp-PWM .

There are a few things that were needed, but the core of the solution looks like that :

`
Enter code here…void InitPinFS()
{
LoadDeviceTreeModule(std::string(“cape-universaln”)); //Modified for kernel 4.1.6 and cape-universaln
//std::string pinModule = std::string(“sc_pwm_”) + GetPinName();
//LoadDeviceTreeModule(pinModule);

//Find chip name
std::string m_chipAddress;
std::string m_pwmNumber;
//EHRPWM0A
if (m_pinName == “P9_22” || m_pinName == “P9_31”) {
m_chipAddress = “48300200.ehrpwm”;
m_pwmNumber = “pwm0”;
}
//EHRPWM0B
if (m_pinName == “P9_21” || m_pinName == “P9_29”) {
m_chipAddress = “48300200.ehrpwm”;
m_pwmNumber = “pwm1”;
}
//EHRPWM1A
if (m_pinName == “P9_14” || m_pinName == “P8_36”) {
m_chipAddress = “48302200.ehrpwm”;
m_pwmNumber = “pwm0”;
}
//EHRPWM1B
if (m_pinName == “P9_16” || m_pinName == “P8_34”) {
m_chipAddress = “48302200.ehrpwm”;
m_pwmNumber = “pwm1”;
}
//EHRPWM2A
if (m_pinName == “P8_19” || m_pinName == “P8_45”) {
m_chipAddress = “48304200.ehrpwm”;
m_pwmNumber = “pwm0”;
}
//EHRPWM2B
if (m_pinName == “P8_13” || m_pinName == “P8_46”) {
m_chipAddress = “48304200.ehrpwm”;
m_pwmNumber = “pwm1”;
}
//ECAPPWM0
if (m_pinName == “P9_42”) {
m_chipAddress = “48300100.ecap”;
m_pwmNumber = “pwm0”;
}
//EHCAPPWM2
if (m_pinName == “P9_28”) {
m_chipAddress = “48300200.ecap”;
m_pwmNumber = “pwm0”;
}
std::string m_chipPath = GetOCPPath() + “subsystem/devices/” + m_chipAddress + “/pwm/” ;

std::string pinInterfacePath = m_chipPath + GetFullNameOfFileInDirectory(m_chipPath, “pwmchip”) + “/” + m_pwmNumber + “/”;
m_dutyFilePath = pinInterfacePath + “duty_cycle”;
m_periodFilePath = pinInterfacePath + “period”;
m_polarityFilePath = pinInterfacePath + “polarity”;
m_runFilePath = pinInterfacePath + “enable”;
`

I haven’t looked into the reason why one of the eCAP did not show up, as I only need 6 PWM for the project.

Time for another coffee.

Thanks a lot for your replies.

Cheers,

Pierre-Louis

There’s another eCAP instance in PRUSS, and timers 4-7 can also be used as PWM outputs. All of these are available on the headers, so that makes 13 usable pwm outputs in total.

(If you’re really desperate you can get at the remaining eCAP instance by either sacrificing your serial console or by grabbing it from pin 19 of the jtag header. In the latter case you can’t have a μSD card inserted since card detect is on the same line.)

And of course a little PRU program could get you a whole bunch of extra PWM outputs.

Cheers Matthijs, that’s the answer that I was looking for.

It looks like the PRU is a feature that will require my attention shortly.

Cheers,

PL

Hello Pierre,

I honestly do not know the eCAP / PWM modules at all, but I suspect that you would not necessarily need to use the PRU to access these modules. Pretty much anything that can be accessed via the PRU’s can be accessed from a C program through /dev/mem/ + mmap(). Perhaps though, since I have no hands on experience with the eCAP/ PWM modules . . .maybe I’m missing something ?

With all that said, I’m also having a hard time visualizing the need for PRU based PWM, unless one needs to make changes very rapidly. That does not mean I know everything though . . . because obviously I do not.

I honestly do not know the eCAP / PWM modules at all, but I suspect that
you would not necessarily need to use the PRU to access these modules.

Although I haven't yet tried to use the eCAP in PRUSS, I don't think
there's any particular obstacle to using it directly. You don't need to get
the PRU cores involved if you don't want to. I just mentioned them as a way
to create *even more* PWM outputs (via PRU's GPOs) if you'd need them.

Pretty much anything that can be accessed via the PRU's can be accessed
from a C program through /dev/mem/ + mmap().

Correct, pretty much anything can be used from userspace like that,
although some people will frown at you for bypassing the kernel if you do.

A bit more elegant than using /dev/mem is using the uio_pdrv_genirq driver,
which lets you set the permissions of the device using udev hence avoid the
need to be root, and also allows you to receive irqs in userspace. If the
device tree node(s) also have the appropriate ti,hwmods property (e.g. if
you simply reuse an existing DT node but alter it to use uio) then the
kernel will also enable the module clocks for you when you open the device,
saving you from manually fiddling with PRCM. This is an example I made
earlier for doing this for the ADC, but other modules are similar:
http://pastebin.com/GrHwgYiR

Beware btw that the eHRPWM modules have an additional little complication:
their counters also need to be ungated via a control module register. The
purpose of this is to allow the modules to run synchronized by configuring
them to the same frequency and then ungating their counters simultaneously.
The control module however requires privileged writes, and any write
performed in a normal way from userspace gets ignored. Fortunately, you can
easily get the kernel to perform the write for you, e.g.
using process_vm_readv( getpid(), ... ).

Matthijs

Correct, pretty much anything can be used from userspace like that, although some people will frown at you for bypassing the kernel if you do.

So maybe to you, and others this seems like an argument. But it’s not. Just a point I feel the need to put out there. Using /dev/mem/ + mmap() is in no way different than using the PRU’s to do the same thing. That is, in the context of accessing the registers. Obviously though the PRU’s have the deterministic aspect on their side. Anyhow, the “drivers” have no idea what is going on in either case. Assuming a driver is even used. But I’ve found that one can simply “poke” the correct registers, in the correct order to use the ADC module - Without even using a driver module . . . So I would assume most, if not all peripheral modules would be the same.

A bit more elegant than using /dev/mem is using the uio_pdrv_genirq driver, which lets you set the permissions of the device using udev hence avoid the need to be root, and also allows you to receive irqs in userspace. If the device tree node(s) also have the appropriate ti,hwmods property (e.g. if you simply reuse an existing DT node but alter it to use uio) then the kernel will also enable the module clocks for you when you open the device, saving you from manually fiddling with PRCM. This is an example I made earlier for doing this for the ADC, but other modules are similar: http://pastebin.com/GrHwgYiR

Actually, my ideal “elegant” way to use the registers for peripherals. Would be by using the PRU’s through remoteproc / rpmsg. I’ve been reading up on that subject, as well as the general usage of the PRU’s, and I have to say: It’s so far a very steep learning curve. For me though, I’ve been mostly a high level language developer most of my . . . hobbyist “career”. So anything low level, can be an adventure for me. It seems as though I should read up on uio_pdrv_genirq though*.* Granted, “IRQ” to me spells “trouble”. As when using hardware like this, as such. I’m typically going for all out performance. I’ve found in all cases so far, that system interrupts, tend to slow down code considerably . . .The uio driver for the ADC I’ve found to be like this so far. But I am in no way an expert, and to be perfectly honest I do not know the layout of uio buffer ( /dev/uio:device0) at all. I am able to read the first 32 bits, and mask out the data, and ID fields, but have no idea how to access the whole buffer, or how it’s laid out - Yet. I mostly got bored with it after realizing that I can pretty much max out the ADC, in userspace by using /dev/mem/ . . . as much as one can anyway, before system latency starts taking it’s toll - Even on an RT kernel.

I’ll check out that pastbin of yours, so see if I can glean what I’ve been missing thus far.

I’ll check out that pastbin of yours, so see if I can glean what I’ve been missing thus far.

Ah, perhaps information and things I had not considered yet. But not what I was hoping for :wink: config files, and device tree files I can usually figure out fairly easily. Was hoping to “spot” some uio buffer layout goodness :slight_smile:

*Correct, pretty much anything can be used from userspace like that,

although some people will frown at you for bypassing the kernel if you do.*

So maybe to you, and others this seems like an argument.

No, I said *some people*. :slight_smile: I personally much prefer to directly access
peripherals than go through thick layers of overhead to ultimately
accomplish exactly the same time. I even setup DMA transfers from userspace
:wink:

Actually, my ideal "elegant" way to use the registers for peripherals.
Would be by using the PRU's through remoteproc / rpmsg.

Why? Using PRUs to access peripherals makes no sense to me either, unless
you need *really* tight timing.

It seems as though I should read up on *uio_pdrv_genirq *though*. *Granted,
"IRQ" to me spells "trouble". As when using hardware like this, as such.

An uio device in general behaves like a chunk of /dev/mem, corresponding to
the register range declared in DT. It's a little safer than /dev/mem since
the device only gives access to that predeclared range, and as I mentioned
you get benefits like being able to have the kernel automatically enable
the module in PRCM when you open the device (and disable it again after
closing it).

The "genirq" is in the name because it also includes a generic way to
receive IRQs if you wish to: you should declare the interrupt in DT, after
opening the device you write (u32)1 to it to enable interrupts and
read(u32) from it to wait for an interrupt, or you can use poll() or any
other event loop. At most one IRQ is supported per device, but you can just
declare extra uio devices in DT consisting of just an IRQ (no regs) if you
need them.

IRQ latency test on an RT kernel, with a userspace process waiting for a
GPIO-triggered IRQ and pulsing another GPIO in response. Not great but
good enough for many purposes:

The uio driver for the ADC

No such thing exists. Are you confusing uio with iio? I never really
explored iio in much detail, using direct userspace access was more
convenient.

Was hoping to "spot" some uio buffer layout goodness :slight_smile:

I have absolutely no idea what you could mean by that.

irq-latency2.png

Why? Using PRUs to access peripherals makes no sense to me either, unless you need really tight timing.

Determinism. I do agree with what you’re saying, but sometimes, you need things to happen at exactly . In userland, you may be able to achieve that most of the time, but you will not be able to achieve that all of the time. Even if that time interval does not happen very often. As an added bonus, we get to offload the main core in the process :slight_smile:

No such thing exists. Are you confusing uio with iio? I never really explored iio in much detail, using direct userspace access was more convenient.

Yes, I did confuse iio with uio. Or more correctly thinking of one thing, while speaking about another . . hah. Been reading too much lately, about too many things, I think. uio I suppose I know of but have not done much reading on it yet. uio:device0 should have been iio:device0

I have absolutely no idea what you could mean by that.

So /dev/iio:device0 is a buffer for the ADC when operating in continuous mode . . . long story short - I now consider iio a waste of time. In a way, like you, I do not like unnecessary abstraction layers. Especially those that seem to fight against ones willingness to learn . . .Especially difficult ones, that seem to require a “secrete handshake” to get any decent information about. E.G. buy a high dollar Linear technologies device. Maybe I’m over stating things a bit ? Maybe . . .but either way I’m done wasting my time with iio.

No, I said some people. :slight_smile: I personally much prefer to directly access peripherals than go through thick layers of overhead to ultimately accomplish exactly the same time. I even setup DMA transfers from userspace :wink:

Ok, well I was just putting that out there . . . One of the biggest things in my mind about what makes Linux great, is it’s flexibility. So when so-n-so tries to tell me “you can’t do x.y.z, because of a.b.c . . .” Not only do I end up laughing to myself over it, I usually let them have “what for”, because that’s how I am :wink:

irq-latency2.png

Determinism. I do agree with what you're saying, but sometimes, you need
things to happen at exactly <some time interval >. In userland, you may be
able to achieve that most of the time, but you will not be able to achieve
that all of the time. Even if that time interval does not happen very often.

Determinism is relative. RT kernels should in principle allow things to
happen within <some time interval> all of the time, provided the interval
is not too tight, you correctly configured priorities, and you don't run
into a seriously misbehaving driver.

PRUSS of course offers absolute determinism, but only if you stay within
the subsystem. As soon as you start using peripherals outside it your
timing starts getting affected by interconnect traffic from the cortex-A8.

For some applications even the jitter due to competing interconnect traffic
is already bad. For most however the jitter of an RT kernel is probably
fine.

As an added bonus, we get to offload the main core in the process :slight_smile:

I'd consider PRU to be a more precious resource than cortex-A8 cycles
though.

So /dev/iio:device0 is a buffer for the ADC when operating in continuous
mode

Ah, yes I also use a tiny ring buffer for ADC which I reserved (using DT)
in OCMC ram. Configured DMA to write the ADC data to the buffer whenever
it's available, and userspace can simply read the latest measurements from
the buffer whenever it needs them.

I now consider iio a waste of time. In a way, like you, I do not like
unnecessary abstraction layers. Especially those that seem to fight against
ones willingness to learn

I have to admit I didn't look at it very long, but to me it also seemed
overly complicated and opaque.

Anyhow, I noticed we've drifted slightly off-topic here... :wink:

Anyhow, I noticed we’ve drifted slightly off-topic here… :wink:

Holy molly … we did, however you guys provided me enough info leads for a whole year of insomnia.

Nevertheless there are a point I’d like to bump up :

Correct, pretty much anything can be used from userspace like that, although some people will frown at you for bypassing the kernel if you do.

Righteo, so far I was using the somewhat slow (but easy) way thyrough FS, however I am stuck with running my program as root, as I can’t access the PWM (or hardware) as normal user. Using the mmap() method would be permission free, would it ?

Cheers guys,

PL

you guys provided me enough info leads for a whole year of insomnia.

haha, sorry :slight_smile:

Correct, pretty much anything can be used from userspace like that,

although some people will frown at you for bypassing the kernel if you do.

Righteo, so far I was using the somewhat slow (but easy) way thyrough FS,
however I am stuck with running my program as root, as I can't access the
PWM (or hardware) as normal user. Using the mmap() method would be
permission free, would it ?

other way around: you can simply change owner/group/permissions in sysfs if
you want to (although obviously it won't persist across reboots, hence
would need to be done e.g. in an udev rule).

/dev/mem however always requires root permissions to open. You can of
course open it and then drop permissions (and close the descriptor after
mmapping the regions you need). The same is true for other files and
devices of course, including sysfs files.

uio is the cleanest here: the pastebin I linked to actually included an
example udev rule for setting group and permissions.

sysfs files aren't devices hence you can't set their permissions in the
same way but need to do something ugly like RUN="/bin/chmod ...". It's
still wise to do it in an udev rule rather than a fixed startup script
since it may run too early and the files may not exist yet.

Matthijs,

Where would be a good start to read up on uio ? I’ve been reading around, but have not found anything of much use yet. Also, that pastebin you gave, this is everything needed to use an uio adc ?

But for instance, assuming the pastebin you gave is everything needed( which would be awesome ), how do I know how this adc uio “object” is set up ? e.g. how would i write a userspace driver for it ? I have so many questions, but no idea where to start looking for answers . . .

Where would be a good start to read up on uio ? I've been reading around,
but have not found anything of much use yet.

The official doc is the UIO Howto
<https://www.kernel.org/doc/htmldocs/uio-howto/>, but it's very
PCI-oriented. The basic assumption is that there are only a few irq lines
which are shared, hence even if you want to do most things in userspace a
tiny kernel driver is still needed to do whatever is necessary to get the
irq deasserted before delivering it to userspace. The uio kernel framework
is designed to facilitate that.

uio_pdrv_genirq is a simple uio driver which "handles" the irq simply by
disabling it (until reenabled by userspace), which requires that the irq
line isn't shared. This is fine since the AM335x doesn't use shared irq
lines, its interrupt controller for the cortex-A8 has 128 inputs.

Also, that pastebin you gave, this is *everything* needed to use an uio
adc ?

Yep. I don't think the device tree snippet works as overlay though, you may
need to add it to the main dts. (I never use overlays so I haven't tested
that.)

But for instance, assuming the pastebin you gave is everything needed(
which would be awesome ), how do I know how this adc uio "object" is set up
?

As the comment at the bottom says, /dev/uio/adc would appear.

e.g. how would i write a userspace driver for it ?

open /dev/uio/adc, mmap() it (offset 0 size 4KB) to obtain access to its
registers. See TRM chapter 12 for usage (yes, "Touchscreen Controller",
that's the one).

If you want to receive interrupts, write (u32)1 to the fd to enable the
irq. When it occurs, the irq is disabled, the fd becomes readable and you
read an u32 from it (value will be 1).

A word of caution: once you've enabled the ADC, if you want to reconfigure
it, first disable all steps (set STEPENABLE to 0) and wait until it is idle
(check ADCSTAT register, STEP_ID and FSM_BUSY will both indicate idle).
Failure to do so can lock up the ADC state machine in a way that requires
reboot to clear. Most peripherals have some kind of reset functionality but
oddly the ADC does not.

You can also peek at my header: http://gerbil.xs4all.nl/adc.h (the headers
it depends on can be found in https://github.com/dutchanddutch/jbang )

My code is in a bit eccentric style of C++14 though :stuck_out_tongue: but even if you
don't use it directly the comments in it may be useful.

Word of caution: if you do use it; since I don't make any config registers
volatile (it doesn't really work with bitfields anyway) you need an
occasional compiler barrier to avoid reordering, e.g. between setting the
configuration and enabling it. You can #include <util/device.h> and use
write_barrier(adc); (or write_barrier(*adc) if it's a pointer rather than a
reference)

sorry, #include "util/device.h"
it's obviously not a system header (but also one you can find in jbang)

Happens automagically when you open /dev/uio/adc (and the kernel will
disable the clock again when you close it).

The reason for that is that my DTS fragment reuses the existing DT node for
the ADC. A standalone definition would be:

tscadc@44e0d000 {
  reg = <0x44e0d000 0x1000>;
  interrupt-parent = <&intc>;
  interrupts = <16>;
  ti,hwmods = "adc_tsc";
  compatible = "uio";
  uio-alias = "adc";
};

The magic clock enabling part is due to "ti,hwmods", which associates the
node with platform data still hardcoded into the kernel. I'm not entirely
sure how the mechanism works exactly, and my understanding is that it's
destined to become deprecated eventually ("replaced by genpd along the
lines of simple-pm-bus"). I don't know how things will look after that.

(the two child nodes declared in the main DT are ignored by uio and
harmless)

Matthijs

PS although your reply was by direct email I'm sending this to the list
again since, although off-topic, I think it may still be of interest to
other readers.