I've been analyzing how the sensor works via the same workflow and tools
I made for HM5065. GC2145 was even nicer in this regard, since it doesn't need
stopping/restarting the stream when changing settings in the
sensor's registers.
My setup is:
use a basic camera driver that powers up the camera sensor and exposes V4L2
debug interface that allows modifying arbitrary camera sensor registers
run my cam-dmabuf app that grabs frames from the camera and
displays them on screen using a DRM buffer import using a video plane scaled to
the whole screen
simultaneously run cam-cli CLI tool to test/experiment with
modifications of various camera sensor registers over the debug interface
simultaneously run logic-probe tool that can capture and show
CSI signal capture in pulseview, when necessary (it can capture at
6MHz sampling rate)
This is pretty neat, since I can both see the signalling:
And the effect of register value changes on the screen in real-time. This
makes testing and understanding the registers very easy.
Powerup
Powerup requires enabling power supplies in the following order:
Power down allows to disable power supplies at once. There's no „at
once“ in reality, so we just use the reverse order of the powerup
sequence.
GPIOs that control RESET and PWDN pins are put into hi-Z mode, to avoid any
kind of leakage. There are pullup resistors that ensure the signals are in
defined state when the power is still applied to the sensor.
Reset
Reset can be done via GPIO. There's no need to initialize the sensor
register values manually to the reset values as the BSP driver does. The
registers are initialized automatically.
Power save
Power save mode can be entered by pulling PWDN signal high. Register values
are preserved in PWDN mode and the CSI signal drivers are put in hi-Z mode.
Clocks
We need to be able to understand and control clocks in order to control the
upper margin for framerate and lower margin for exposure. The sensor can use
internal PLL to derive clock signals from externally supplied MCLK.
So far I have the following understanding of the clock tree, which looks
like this:
2PCLK = MCLK / D * N / M where:
D = 2 when P0:0xf7[1] is set, otherwise 1
N = 2..17 set in P0:0xf8[5:0] (register range allow higher N,
but working values are up to 17)
M = 1..16 set in P0:0xfa
K = 1..8 set in P0:0xf7[6:4]
PCLK signal that can be measured at the output from the sensor is derived
from 2PCLK clock under these circumstances:
scaler is not used (P0:0xfd = 0) PCLK signal has frequency
2PCLK and respects the duty cycle set in P0:0xfa.
scaler is used (P0:0xfd = 1) PCLK signal has half the frequency
of 2PCLK and the duty cycle is 1:1.
It's possible to bypass the PLL by setting bit P0:0xf9[0].
DCLK has documented upper limit in the datasheet at 168MHz. Experimentally,
I've found that DCLK is limited to around 200MHz (N=17 and
MCLK=12MHz ⇒ DCLK=204MHz). Althoug N=17
causes some color distortion that can be corrected by using
N=16.
Parallel interface
When bypassing the PLL, enabling MCLK DIV2, and maxing out M, we can achieve
2PCLK of 750kHz. At this speed, it's possible to get unaliased captures of all
CSI signals. This is useful for understanding effects of various
horizontal/vertical timing options.
Parallel interface is configured in register P0:0x86.
There are a lot of moving bits here, so let us reference all signals to PCLK
cycle. Non-inverted PCLK cycle is defined as a periodic HIGH LOW pattern and all
signals change at the start of the cycle.
So for example when P0:0x86 is set to 0x04, the
signals look like this (PCLK duty cycle is set to 1:8):
So with this configuration CSI controller in the SoC should sample signals at
the falling edge of PCLK, when the signals are stable. (One exception is end of
VSYNC pulse, which chnages in the second half of the PCLK cycle, for unknown
reasons.)
The bits in P0:0x86 mean:
[0] 0 – VSYNC pulse high prior to start of the frame (ie VSYNC low during
the frame transmission)
[1] 0 – HSYNC pulse high prior to start of the row (ie HSYNC low during
the row transmission)
[2] 1 – PCLK is not inverted (HL cycle) – see above
[2] 0 – PCLK is inverted (LH cycle):
[3] 1 – PCLK is gated when other signals are not changing (see
image below)
[6] 1 – HSYNC changes in the middle of the PCLK cycle
[6] 0 – HSYNC changes at the beginning of the PCLK cycle
[7] 1 – DATA changes in the middle of PCLK cycle
[7] 0 – DATA changes at the beginning of PCLK cycle
CIS – CMOS image sensor
control / readout
CMOS image sensor circuitry works by reading out pixels from the sensor array
row by row, passing voltage through an analog amplifier to A/D converter and
then to ISP (digital post-processing of RAW sensor data).
GC2145 allows to control this process by:
setting column/row offsets where to start the readout
setting number of columns/rows to read out
reading out by skipping even rows and/or columns
changing the direction of the readout (h/v flips)
The speed at which data are read from the sensor determines the speed at
which it will be sent on the CSI bus. The way data are read out from the sensor,
also determines the field of view. So there are several concerns here:
To achieve the maximum resolution, we need to scan the entire pixel array
consisting of 1616×1232 pixels.
To achieve the maximum field of view we also need to scan across the entire
pixel array, but we can improve frame rate, by skipping rows and columns.
To achieve higher frame rate we have to limit the amount of pixels scanned,
though we're limited by the need for enough exposure time.
Alternatively, for better picture quality, we can use the scaler, and scan
out the full image and scale it to half the size by averaging neighboring pixel
values.
With row/column skipping, we should be able to comfortably achieve 30 FPS at
800×600 with full FOV.
Manipulating vertical blank time setting allows to fix framerate while
allowing to change exposure without it affecting frame rate.
Sensor specification lists the framerate limit at 30 FPS at 1600×1200. A83T
CSI limit is 30FPS at 1280×720 (translating to about 50–60MHz PCLK
limit).
Strategy for format selection
Subdev API allows to select frame rate and resolution. Based on this
information sensor driver will have to decide what settings to use for the
sensor, to achieve the best image quality.
Following strategy will be used:
determine if scaler can be used: W and H are both smaller than half the
sensor pixel array size, use the scaler
determine if requested FPS can be achieved with the scaler enabled, if not
use row/column skiping instead
determine if subsampling can be used to increase the FOV for the current
setting
select PLL settings based on whether scaler is used or not
determine AEC settings to get proper exposure for selected PLL settings
determine vertical blank time for the selected framerate
Shutting down
the boot CPU and regulator powerdown
At this point, we have the Linux side of the suspend working. Now we need to
focus on crust, to make it perform the actual power management tasks.
We'll implement a fixed function suspend process. This means, that we'll not
be supporting individual control of the CPUs or clusters. We know that Linux
prepares the system state so that only one CPU is running, and all other CPU
cores are shut down, so SCP only has to:
stop and power down the last CPU
power down it's cluster
turn off both CPU cluster regulators (Linux doesn't do this)
do some other power management tasks
Enable DRAM self-refresh
Turn off memory controller
Shut down some PLLs
etc.
Now it can wait for interrupts and resume by reversing the steps above.
Sending a suspend
message to SCP from Linux
Now for the fun stuff. I've tried to add support for sending SCPI message
from the suspend handler, and it doesn't work well.
Apparently, mailbox client used by SCPI protocol driver, uses some
functionality that is already suspended at this stage (timekeeping). Bummer. We
need to send message to SCP before syscore_suspend().
The last available hook that is called prior to syscore_suspend
is prepare_late, but that's still called prior to disabling
secondary CPUs. Tough luck.
The problem turns out to be just in mailbox core code, and it's failry
simple to fix:
From f72801cbfe7fd53b59e4996f9948a6f2cc85977e Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sat, 2 Nov 2019 15:09:01 +0100
Subject: [PATCH 2/6] mailbox: Allow to run mailbox while timekeeping is
suspended
This makes it possible to send messages from CPU suspend finisher.
We simply implement cl->tx_block using a busywait loop when
timekeeping is suspended, instead of using hrtimer.
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
drivers/mailbox/mailbox.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
index 0b821a5b2db8..e5eb4bf447f8 100644
--- a/drivers/mailbox/mailbox.c+++ b/drivers/mailbox/mailbox.c
@@ -82,9 +82,12 @@ static void msg_submit(struct mbox_chan *chan)
exit:
spin_unlock_irqrestore(&chan->lock, flags);
- if (!err && (chan->txdone_method & TXDONE_BY_POLL))- /* kick start the timer immediately to avoid delays */- hrtimer_start(&chan->mbox->poll_hrt, 0, HRTIMER_MODE_REL);+ if (!err && (chan->txdone_method & TXDONE_BY_POLL)) {+ if (!timekeeping_suspended) {+ /* kick start the timer immediately to avoid delays */+ hrtimer_start(&chan->mbox->poll_hrt, 0, HRTIMER_MODE_REL);+ }+ }
}
static void tx_tick(struct mbox_chan *chan, int r)
@@ -260,6 +263,24 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg)
msg_submit(chan);
+ if (chan->cl->tx_block && timekeeping_suspended) {+ int i = chan->cl->tx_tout * 10;+ bool txdone;++ while (i--) {+ txdone = chan->mbox->ops->last_tx_done(chan);+ if (txdone) {+ tx_tick(chan, 0);+ return 0;+ }++ udelay(100);+ }++ tx_tick(chan, -ETIME);+ return -ETIME;+ }+
if (chan->cl->tx_block) {
unsigned long wait;
int ret;
--
2.23.0
We just have to avoid using hrtimer when
timekeeping_suspended is 1, and busywait instead.
Signalling SCP to suspend
I don't want to deal with complex power sequencing ATM, so I'll reuse
existing SCPI_CMD_SET_SYS_PWR_STATE SCPI message to signal SCP to
initiate system suspend. Normally this message is only used for reset/poweroff
requests.
First, we need to extend scpi_ops to be able to send this
message from CPU suspend finisher callback:
From 37f517b67a0267ce7c09ea8132ebba0966236fb6 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sat, 2 Nov 2019 15:14:10 +0100
Subject: [PATCH 4/6] firmware: scpi: Add support for sending a
SCPI_CMD_SET_SYS_PWR_STATE msg
This is NOT the right message to signal SCP we want to suspend the
system, but we use it anyway, because we simply want to do a fixed
function suspend sequence, instead of more complicated and granular
cluster/cpu/system power management.
Normally we'd signal system suspend by sending a CSS power state
message. I guess that can be done later, if wanted.
For now, Linux will kill all secondary CPU cores via MCPM, and SCP
will kill the last CPU, and suspend the system.
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
drivers/firmware/arm_scpi.c | 10 ++++++++++
include/linux/scpi_protocol.h | 1 +
2 files changed, 11 insertions(+)
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index 02ca94faa162..16128306b23d 100644
--- a/drivers/firmware/arm_scpi.c+++ b/drivers/firmware/arm_scpi.c
@@ -184,6 +184,7 @@ enum scpi_drv_cmds {
CMD_SENSOR_VALUE,
CMD_SET_DEVICE_PWR_STATE,
CMD_GET_DEVICE_PWR_STATE,
+ CMD_SET_SYS_PWR_STATE,
CMD_MAX_COUNT,
};
@@ -200,6 +201,7 @@ static int scpi_std_commands[CMD_MAX_COUNT] = {
SCPI_CMD_SENSOR_VALUE,
SCPI_CMD_SET_DEVICE_PWR_STATE,
SCPI_CMD_GET_DEVICE_PWR_STATE,
+ SCPI_CMD_SET_SYS_PWR_STATE,
};
static int scpi_legacy_commands[CMD_MAX_COUNT] = {
@@ -215,6 +217,7 @@ static int scpi_legacy_commands[CMD_MAX_COUNT] = {
LEGACY_SCPI_CMD_SENSOR_VALUE,
-1, /* SET_DEVICE_PWR_STATE */
-1, /* GET_DEVICE_PWR_STATE */
+ LEGACY_SCPI_CMD_SYS_PWR_STATE,
};
struct scpi_xfer {
@@ -777,6 +780,12 @@ static int scpi_device_set_power_state(u16 dev_id, u8 pstate)
sizeof(dev_set), &stat, sizeof(stat));
}
+static int scpi_sys_set_power_state(u8 pstate)+{+ return scpi_send_message(CMD_SET_SYS_PWR_STATE, &pstate,+ sizeof(pstate), NULL, 0);+}+
static struct scpi_ops scpi_ops = {
.get_version = scpi_get_version,
.clk_get_range = scpi_clk_get_range,
@@ -793,6 +802,7 @@ static struct scpi_ops scpi_ops = {
.sensor_get_value = scpi_sensor_get_value,
.device_get_power_state = scpi_device_get_power_state,
.device_set_power_state = scpi_device_set_power_state,
+ .sys_set_power_state = scpi_sys_set_power_state,
};
struct scpi_ops *get_scpi_ops(void)
diff --git a/include/linux/scpi_protocol.h b/include/linux/scpi_protocol.h
index ecb004711acf..a695d43c91f9 100644
--- a/include/linux/scpi_protocol.h+++ b/include/linux/scpi_protocol.h
@@ -64,6 +64,7 @@ struct scpi_ops {
int (*sensor_get_value)(u16, u64 *);
int (*device_get_power_state)(u16);
int (*device_set_power_state)(u16, u8);
+ int (*sys_set_power_state)(u8);
};
#if IS_REACHABLE(CONFIG_ARM_SCPI_PROTOCOL)
--
2.23.0
And now we can call it from A83T specific PM code:
From 9e4886ecf37780ef65819f040c89a0a460f903a2 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sat, 2 Nov 2019 15:21:04 +0100
Subject: [PATCH 6/6] ARM: sunxi: Use SCPI to send suspend message to SCP on
A83T
We use undefined value of 3, to mean SUSPEND_SYSTEM. SCP should:
- kill CPU0- kill cluster 0- shutdown power to both clusters (Linux MCPM doesn't do that)
...
- reverse all of the above on interrupt
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
arch/arm/mach-sunxi/sunxi.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/arch/arm/mach-sunxi/sunxi.c b/arch/arm/mach-sunxi/sunxi.c
index 200f2c17fa17..64a70dccbf67 100644
--- a/arch/arm/mach-sunxi/sunxi.c+++ b/arch/arm/mach-sunxi/sunxi.c@@ -16,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/reset/sunxi.h>
+#include <linux/scpi_protocol.h>
#include <linux/suspend.h>
#include <asm/mach/arch.h>
@@ -98,8 +99,18 @@ static int sun8i_a83t_pm_valid(suspend_state_t state)
static int sun8i_a83t_suspend_finish(unsigned long val)
{
- // don't do much- cpu_do_idle();+ struct scpi_ops *scpi;++ scpi = get_scpi_ops();+ if (scpi && scpi->sys_set_power_state) {+ //HACK: use invalid state to mean: suspend last CPU and the system+ scpi->sys_set_power_state(3);+ cpu_do_idle();+ } else {+ // don't do much if scpi is not available+ cpu_do_idle();+ }+
return 0;
}
--
2.23.0
Testing
I've extended crust SCP firmware to print a message to serial console, when
it receives the above message from the kernel.
And it seems to work!
# ./load --reset
ARISC is already inreset# ./load scp.bin
Asserting ARISC reset
Writing exception vectors
Writing firmware (8732/64512 bytes used)
Deasserting ARISC reset
INFO: Watchdog enabled
INFO: SCPI: Initialization complete
# echo mem > /sys/power/state
PM:suspend entry (deep)
Filesystems sync:0.052 seconds
Freezing user space processes ... (elapsed 0.002 seconds)done.
OOM killer disabled.
Freezing remaining freezable tasks ... (elapsed 0.001 seconds)done.
musb-sunxi 1c19000.usb: Error unknown readb offset 112
Disabling non-boot CPUs ...
calling scpi
INFO: SCP got mesasge 5
INFO: SCP got suspend mesasge 3# [... sleeps here ...]
Enabling non-boot CPUs ...
CPU1 is up
CPU2 is up
CPU3 is up
CPU4 is up
CPU5 is up
CPU6 is up
CPU7 is up
musb-sunxi 1c19000.usb: Error unknown writeb offset 112
usb 1-1:reset high-speed USB device number 2 using ehci-platform
usb 1-1.3:reset full-speed USB device number 3 using ehci-platform
OOM killer enabled.
Restarting tasks ... done.
PM:suspend exit
Upstreaming
So I've sent out a bunch of different patches upstream that should help with
suspend/resume, fix CPU hotplug bug, and upstream the touch panel support
in DTS:
(EDIT: looks like all the patches were accepted upstream)
I've also tested disabling MUSB in the DTS (and it saves about 40–60mW) of
power, compared to using MUSB's suspend hook.
Crust and system sleep
My next steps are in adding some debugging functionality to crust, so that
I can experiment with shutting down the last CPU from arisc core, instead of
simply running the WFI on the last core, as I do now.
During sleep I want to be able to communicate with crust firmware over UART
console and issue interactive commands to read/write SoC and PMIC registers.
With this I should be able to quickly experiment with this stage of the
suspend process, like this:
Linux shuts down what it can via device suspend and MCPM code (all except
the boot CPU)
Linux sends SCPI message to crust to do its part of suspending
the system
Linux runs WFI on boot CPU
Crust takes over and starts listening to UART0 for above mentioned
commands
Using this I should be able to quickly experiment with various suspend
related tasks, without the need to re-compile crust/kernel, and I should be
able to immediately see effects of individual shutdown steps on power
consumption. All this with some simple scripting over serial port.
Baseline power consumption in suspend to idle and all my patches at the
moment is 1.12W.
Current monitor
So I've made a power consumption monitoring tool for the tablet and
calibrated it. It sends values periodically over USB-serial port. Now I can
monitor and log power consumption without having to depend on the access to
the PMIC.
Here's a boot/shutdown cycle log for example (the values are averages over
250ms, but INA226 can be set up to also provide fast non-averaged measurements
at sub-millisecond intervals if necessary, since sliding window averaging can be
misleading):
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4307 mV I=182773 uA P=787 mW
U=4270 mV I=401916 uA P=1716 mW
U=4261 mV I=455855 uA P=1942 mW
U=4259 mV I=473732 uA P=2017 mW
U=4260 mV I=469108 uA P=1998 mW
U=4260 mV I=469108 uA P=1998 mW
U=4260 mV I=469725 uA P=2001 mW
U=4259 mV I=470958 uA P=2005 mW
U=4259 mV I=471882 uA P=2009 mW
U=4259 mV I=471882 uA P=2009 mW
U=4259 mV I=471882 uA P=2009 mW
U=4259 mV I=473115 uA P=2014 mW
U=4259 mV I=473732 uA P=2017 mW
U=4259 mV I=474040 uA P=2018 mW
U=4259 mV I=474040 uA P=2018 mW
U=4259 mV I=474656 uA P=2021 mW
U=4259 mV I=474656 uA P=2021 mW
U=4259 mV I=474348 uA P=2020 mW
U=4259 mV I=474656 uA P=2021 mW
U=4259 mV I=475889 uA P=2026 mW
U=4259 mV I=475581 uA P=2025 mW
U=4259 mV I=476197 uA P=2028 mW
U=4259 mV I=476505 uA P=2029 mW
U=4259 mV I=476197 uA P=2028 mW
U=4259 mV I=475889 uA P=2026 mW
U=4259 mV I=475889 uA P=2026 mW
U=4259 mV I=477122 uA P=2032 mW
U=4258 mV I=476505 uA P=2028 mW
U=4259 mV I=477430 uA P=2033 mW
U=4258 mV I=492841 uA P=2098 mW
U=4251 mV I=518115 uA P=2202 mW
U=4258 mV I=483903 uA P=2060 mW
U=4258 mV I=483595 uA P=2059 mW
U=4258 mV I=483595 uA P=2059 mW
U=4258 mV I=483595 uA P=2059 mW
U=4258 mV I=483286 uA P=2057 mW
U=4257 mV I=482670 uA P=2054 mW
U=4257 mV I=482978 uA P=2056 mW
U=4257 mV I=484211 uA P=2061 mW
U=4258 mV I=483903 uA P=2060 mW
U=4258 mV I=484519 uA P=2063 mW
U=4258 mV I=485136 uA P=2065 mW
U=4257 mV I=484827 uA P=2063 mW
U=4257 mV I=485444 uA P=2066 mW
U=4257 mV I=485444 uA P=2066 mW
U=4245 mV I=553560 uA P=2349 mW
U=4254 mV I=506403 uA P=2154 mW
U=4240 mV I=582841 uA P=2471 mW
U=4218 mV I=712910 uA P=3007 mW
U=4207 mV I=778252 uA P=3274 mW
U=4213 mV I=741882 uA P=3125 mW
U=4206 mV I=787807 uA P=3313 mW
U=4203 mV I=800136 uA P=3362 mW
U=4201 mV I=812464 uA P=3413 mW
U=4187 mV I=892601 uA P=3737 mW
U=4174 mV I=972738 uA P=4060 mW
U=4185 mV I=900615 uA P=3769 mW
U=4163 mV I=1023903 uA P=4262 mW
U=4147 mV I=1118218 uA P=4637 mW
U=4175 mV I=962875 uA P=4019 mW
U=4180 mV I=932670 uA P=3898 mW
U=4185 mV I=911711 uA P=3815 mW
U=4185 mV I=905547 uA P=3789 mW
U=4181 mV I=936985 uA P=3917 mW
U=4199 mV I=820478 uA P=3445 mW
U=4188 mV I=881505 uA P=3691 mW
U=4207 mV I=780410 uA P=3283 mW
U=4202 mV I=814314 uA P=3421 mW
U=4180 mV I=935444 uA P=3910 mW
U=4211 mV I=740958 uA P=3120 mW
U=4208 mV I=779177 uA P=3278 mW
U=4235 mV I=618595 uA P=2619 mW
U=4240 mV I=586848 uA P=2488 mW
U=4233 mV I=640170 uA P=2709 mW
U=4239 mV I=591779 uA P=2508 mW
U=4237 mV I=582533 uA P=2468 mW
U=4222 mV I=695341 uA P=2935 mW
U=4242 mV I=570821 uA P=2421 mW
U=4209 mV I=769622 uA P=3239 mW
U=4209 mV I=751129 uA P=3161 mW
U=4234 mV I=619519 uA P=2623 mW
U=4238 mV I=602875 uA P=2554 mW
U=4244 mV I=568355 uA P=2412 mW
U=4245 mV I=560033 uA P=2377 mW
U=4244 mV I=567430 uA P=2408 mW
U=4239 mV I=590547 uA P=2503 mW
U=4231 mV I=653423 uA P=2764 mW
U=4229 mV I=661437 uA P=2797 mW
U=4235 mV I=608115 uA P=2575 mW
U=4234 mV I=610889 uA P=2586 mW
U=4248 mV I=541540 uA P=2300 mW
U=4248 mV I=536608 uA P=2279 mW
U=4248 mV I=536300 uA P=2278 mW
U=4248 mV I=537841 uA P=2284 mW
U=4247 mV I=544930 uA P=2314 mW
U=4246 mV I=548012 uA P=2326 mW
U=4242 mV I=573595 uA P=2433 mW
U=4254 mV I=499314 uA P=2124 mW
U=4254 mV I=499314 uA P=2124 mW
U=4250 mV I=529211 uA P=2249 mW
U=4248 mV I=533834 uA P=2267 mW
U=4254 mV I=498697 uA P=2121 mW
U=4255 mV I=499622 uA P=2125 mW
U=4254 mV I=500238 uA P=2127 mW
U=4254 mV I=495923 uA P=2109 mW
U=4254 mV I=500547 uA P=2129 mW
U=4254 mV I=496848 uA P=2113 mW
U=4254 mV I=504862 uA P=2147 mW
U=4255 mV I=497773 uA P=2118 mW
U=4254 mV I=498081 uA P=2118 mW
U=4255 mV I=498389 uA P=2120 mW
U=4254 mV I=499005 uA P=2122 mW
U=4255 mV I=488834 uA P=2079 mW
U=4258 mV I=482053 uA P=2052 mW
U=4258 mV I=480821 uA P=2047 mW
U=4258 mV I=479896 uA P=2043 mW
U=4258 mV I=480204 uA P=2044 mW
U=4257 mV I=485752 uA P=2067 mW
U=4250 mV I=524588 uA P=2229 mW
U=4257 mV I=486985 uA P=2073 mW
U=4257 mV I=488218 uA P=2078 mW
U=4257 mV I=487293 uA P=2074 mW
U=4257 mV I=486985 uA P=2073 mW
U=4245 mV I=544622 uA P=2311 mW
U=4220 mV I=693492 uA P=2926 mW
U=4242 mV I=577293 uA P=2448 mW
U=4228 mV I=666677 uA P=2818 mW
U=4247 mV I=540615 uA P=2295 mW
U=4252 mV I=511026 uA P=2172 mW
U=4257 mV I=484519 uA P=2062 mW
U=4255 mV I=491916 uA P=2093 mW
U=4255 mV I=487601 uA P=2074 mW
U=4250 mV I=532601 uA P=2263 mW
U=4257 mV I=484519 uA P=2062 mW
U=4254 mV I=506403 uA P=2154 mW
U=4250 mV I=525512 uA P=2233 mW
U=4221 mV I=689177 uA P=2908 mW
U=4238 mV I=593629 uA P=2515 mW
U=4247 mV I=544314 uA P=2311 mW
U=4246 mV I=546779 uA P=2321 mW
U=4338 mV I=4622 uA P=20 mW
U=4338 mV I=4622 uA P=20 mW
U=4339 mV I=4622 uA P=20 mW
U=4338 mV I=4622 uA P=20 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
U=4339 mV I=0 uA P=0 mW
Next
steps for suspend on A83T, power management optimizations of TBS A711
Create USB-serial based current/voltage sensing device with INA226 (this
should allow for 250uA resolution and continuous logging on a PC)
Now that we can test suspend/resume, finish and upstream the touch panel
support for the tablet.
Make sure usb phy is suspended correctly.
Figure out a way to suspend GPS module with a powerdown packet from kernel
space (or maybe just leave this to userspace?). It looks like GNSS driver
doesn't work very well, and writes lock up sometimes, even from userspace.
Re-enable suspend of mmc# interfaces.
Does NFC chip sleep?
Add support for A83T CPU bins
Suspending devices and CPU
cores
So I've moved to start suspending devices properly on s2idle. So far I've
added suspend support to sun4i-drm driver (display is now properly powered down
during suspend).
I've also looked at creating a simple implementation of suspend to mem that
would just wait on WFI instruction. The difference against s2idle mode would be
that Linux shuts down all secondary CPU cores in suspend to mem mode.
This should work, but does not, because MCPM is somehow broken on A83T.
I can't even hotplug CPU cores using
echo 0 > /sys/devices/system/cpu/cpu#/online anymore.
If I try to offline cpu1 first, I get some kind of soft lockup whenever
I try to offline further cpus.
It's a real mess.
I've been eyeballing the mc_smp.c code for quite some time and
haven't been able to figure out what might be wrong, yet.
At least I've figured out why disabling cpu4 before disabling the rest of the
cluster's cores led to system lockup previously. A83T has CPU0 mapped to bit
4 and not bit 0 of PRCM_PWROFF_GATING_REG. So when disabling
CPU4, parts of the support circuitry for the cluster were disabled instead,
locking up the system.
Maybe it's just 5.4-rc kernel I'm using. I'll try again with 5.3, if CPU
hotplug works there. (Edit: yes, on 5.3 CPU hotplug works well.)
So with Linux 5.3, my CPU hotplug and other fixes, I finally arrived at some
success with suspend to ram:
PM:suspend entry (deep)
Filesystems sync:0.505 seconds
Freezing user space processes ... (elapsed 0.001 seconds)done.
OOM killer disabled.
Freezing remaining freezable tasks ... (elapsed 0.001 seconds)done.
musb-sunxi 1c19000.usb: Error unknown readb offset 112
Disabling non-boot CPUs ...
[ ... sleeps here ... ][ I presses the volume buttonses, aaaand... ]
Enabling non-boot CPUs ...
CPU1 is up
CPU2 is up
CPU3 is up
CPU4 is up
CPU5 is up
CPU6 is up
CPU7 is up
musb-sunxi 1c19000.usb: Error unknown writeb offset 112
usb 1-1:reset high-speed USB device number 2 using ehci-platform
usb 1-1.3:reset full-speed USB device number 3 using ehci-platform
OOM killer enabled.
Restarting tasks ... done.
PM:suspend exit
Now I have to figure out a way to measure the power savings while the system
is suspended. :) My multimeter doesn't like continous current measurement, and
gives out a burning smell after a while.
Analyze which devices are not suspended and why, make them suspend
Measure the current draw from my „virtual“ battery with all devices
suspended
Check if we can get better power savings with A83T specific implementation
of s2idle_ops
After we get maximum power savings from s2idle, it will be time to
investigate deeper sleep states and CPU/cluster powerdown using crust, because
at that point we'll have some baseline suspend state against which to measure
further improvements in power consumption in deeper sleep states. Suspending all
possible devices is necessary anyway.
Suspend to idle (s2idle) – part
2
Resume of WiFi fixed
So it turns out WiFi/Bluetooth issues were related to powering down the WiFi
during suspend.
Adding keep-power-in-suspend to mmc1 fixed the
issues and BT/WiFi and s2idle now successfully performs the entire
suspend/resume cycle without knocking out any hardware.
Also, apparently, it should be possible to wake the tablet using WOL packet
over WiFi.
Suspend/resume log follows. Now, it's time to analyze what sunxi devices
fail to be suspended, because they're missing proper PM code in Linux. The log
should help. I'll try to get better log in the future that will be focussed on
devices that were skipped because they didn't have PM callbacks.
PM:suspend entry (s2idle)
Filesystems sync:0.151 seconds
PM: Adding info for No Bus:vcs63
PM: Adding info for No Bus:vcsu63
PM: Adding info for No Bus:vcsa63
Freezing user space processes ... (elapsed 0.002 seconds)done.
OOM killer disabled.
Freezing remaining freezable tasks ... (elapsed 0.001 seconds)done.
vc vcsa63: direct-complete suspend
vc vcsu63: direct-complete suspend
vc vcs63: direct-complete suspend
vc vcsa6: direct-complete suspend
vc vcsu6: direct-complete suspend
vc vcs6: direct-complete suspend
vc vcsa5: direct-complete suspend
vc vcsu5: direct-complete suspend
vc vcs5: direct-complete suspend
vc vcsa4: direct-complete suspend
vc vcsu4: direct-complete suspend
vc vcs4: direct-complete suspend
vc vcsa3: direct-complete suspend
vc vcsu3: direct-complete suspend
vc vcs3: direct-complete suspend
vc vcsa2: direct-complete suspend
vc vcsu2: direct-complete suspend
vc vcs2: direct-complete suspend
rfkill rfkill1: class suspend
media media0: direct-complete suspend
video4linux v4l-subdev0: direct-complete suspend
video4linux video0: direct-complete suspend
snd-soc-dummy snd-soc-dummy: bus suspend
hci_uart_bcm serial0-0: driver suspend, may wakeup
iio iio:device1: direct-complete suspend
iio trigger0: direct-complete suspend
sound timer: direct-complete suspend
hm5065 2-001f: direct-complete suspend
ep_00: direct-complete suspend
ep_83: direct-complete suspend
net usb0: direct-complete suspend
ep_02: direct-complete suspend
g_ether musb-hdrc.1.auto: direct-complete suspend
g_ether gadget: direct-complete suspend
ep_81: direct-complete suspend
net wlan0: direct-complete suspend
ep_00: direct-complete suspend
ep_81: direct-complete suspend
ep_00: direct-complete suspend
ep_81: direct-complete suspend
usb usb3:type suspend
musb-hdrc musb-hdrc.1.auto: bus suspend, may wakeup
musb-sunxi 1c19000.usb: Error unknown readb offset 112
usb_phy_generic usb_phy_generic.0.auto: bus suspend
musb-sunxi 1c19000.usb: bus suspend
ep_00: direct-complete suspend
rfkill rfkill0: class suspend
ep_81: direct-complete suspend
block mmcblk2boot0: direct-complete suspend
usb usb2:type suspend
ieee80211 phy0: class suspend
bdi 179:16: direct-complete suspend
block mmcblk2boot1: direct-complete suspend
bdi 179:24: direct-complete suspend
block mmcblk2p2: direct-complete suspend
block mmcblk2p1: direct-complete suspend
block mmcblk2: direct-complete suspend
bdi 179:8: direct-complete suspend
vtconsole vtcon1: direct-complete suspend
graphics fb0: direct-complete suspend
drm card0-LVDS-1: direct-complete suspend
drm card0: direct-complete suspend
input event2: direct-complete suspend
input input2:type suspend
sun4i-tcon 1c0c000.lcd-controller: bus suspend
phy phy-1c19400.phy.2: direct-complete suspend
phy phy-1c19400.phy.1: direct-complete suspend
phy phy-1c19400.phy.0: direct-complete suspend
extcon extcon0: direct-complete suspend
sun4i-usb-phy 1c19400.phy: bus suspend
i2c-dev i2c-2: direct-complete suspend
i2c i2c-2: direct-complete suspend
block mmcblk0p2: direct-complete suspend
block mmcblk0p1: direct-complete suspend
i2c-gpio i2c-gpio: bus suspend
leds flash: class suspend
block mmcblk0: direct-complete suspend
bdi 179:0: direct-complete suspend
leds-gpio leds: bus suspend
ep_00: direct-complete suspend
ep_81: direct-complete suspend
mmcblk mmc2:0001: bus suspend
ehci-platform 1c1b000.usb: bus suspend
brcmfmac mmc1:0001:2: bus suspend
usb 1-1.3:1.0: direct-complete suspend
mmcblk mmc0:aaaa: bus suspend
usb 1-1.3:type suspend
usb 1-1:type suspend
usb usb1:type suspend
ehci-platform 1c1a000.usb: bus suspend
sunxi-mmc 1c11000.mmc: bus suspend
brcmfmac mmc1:0001:1: bus suspend
mmc mmc1:0001: bus suspend
sunxi-mmc 1c10000.mmc: bus suspend
sunxi-mmc 1c0f000.mmc: bus suspend
pwrseq_simple wifi_pwrseq: bus suspend
thermal cooling_device1: direct-complete suspend
cpu cpu4: direct-complete suspend
thermal cooling_device0: direct-complete suspend
cpu cpu0: direct-complete suspend
cpufreq-dt cpufreq-dt: bus suspend
sun8i-thermal 1f04000.ths: bus suspend
i2c 1-0028: direct-complete suspend
bma180 1-0018: driver suspend
i2c-dev i2c-1: direct-complete suspend
edt_ft5x06 0-0038: driver suspend, may wakeup
mv64xxx_i2c 1c2b000.i2c: bus suspend
i2c-dev i2c-0: direct-complete suspend
mv64xxx_i2c 1c2ac00.i2c: bus suspend
input event1: direct-complete suspend
input input1:type suspend
sun4i-a10-lradc-keys 1f03c00.lradc: bus suspend, may wakeup
panel-lvds panel: bus suspend
gnss gnss0: direct-complete suspend
gnss-ubx serial1-0: direct-complete suspend
serial serial1: direct-complete suspend
dw-apb-uart 1c28800.serial: bus suspend
dw-apb-uart 1c28400.serial: bus suspend
tty ttyS0: direct-complete suspend
dw-apb-uart 1c28000.serial: bus suspend
backlight backlight: class suspend
pwm-backlight backlight: bus suspend
pwm pwmchip0: class suspend
gpio gpiochip0: direct-complete suspend
gpio gpiochip2: direct-complete suspend
sun8i-a83t-pinctrl 1c20800.pinctrl: bus suspend
sun4i-pwm 1c21400.pwm: bus suspend
rtc rtc0: class suspend
ac100-rtc ac100-rtc: bus suspend
ac100-codec ac100-codec: bus suspend
leds chgled: class suspend
axp20x-leds axp20x-leds: bus suspend
reg-userspace-consumer reg-userspace-consumer: bus suspend
axp20x-usb-power-supply axp20x-usb-power-supply: bus suspend
platform axp20x-ac-power-supply: bus suspend
axp20x-battery-power-supply axp20x-battery-power-supply: bus suspend
iio iio:device0: direct-complete suspend
axp20x-adc axp813-adc: bus suspend
gpio gpiochip414: direct-complete suspend
gpio gpiochip1: direct-complete suspend
axp20x-gpio axp20x-gpio: bus suspend
regulator regulator.27: class suspend
regulator regulator.26: class suspend
regulator regulator.25: class suspend
regulator regulator.24: class suspend
regulator regulator.23: class suspend
regulator regulator.22: class suspend
regulator regulator.21: class suspend
regulator regulator.20: class suspend
regulator regulator.19: class suspend
regulator regulator.18: class suspend
regulator regulator.17: class suspend
regulator regulator.16: class suspend
regulator regulator.15: class suspend
regulator regulator.14: class suspend
regulator regulator.13: class suspend
regulator regulator.12: class suspend
regulator regulator.11: class suspend
regulator regulator.10: class suspend
regulator regulator.9: class suspend
regulator regulator.8: class suspend
regulator regulator.7: class suspend
regulator regulator.6: class suspend
regulator regulator.5: class suspend
regulator regulator.4: class suspend
axp20x-regulator axp20x-regulator: bus suspend
input event0: direct-complete suspend
input input0:type suspend
axp20x-pek axp221-pek: bus suspend
sunxi-rsb 1f03400.rsb: bus suspend
regulator regulator.3: class suspend
reg-fixed-voltage reg-vmain: bus suspend
misc cpu_dma_latency: direct-complete suspend
nvmem sunxi-sid0: direct-complete suspend
event_source CCI_400_r1: direct-complete suspend
misc device-mapper: direct-complete suspend
watchdog watchdog0: direct-complete suspend
misc watchdog: direct-complete suspend
ppp ppp: direct-complete suspend
net bond0: direct-complete suspend
a711 pwr-modem: direct-complete suspend
gpio gpiochip352: direct-complete suspend
gpio gpiochip0: direct-complete suspend
sun8i-a83t-r-pinctrl 1f02c00.pinctrl: bus suspend
block zram0: direct-complete suspend
bdi 254:0: direct-complete suspend
misc apm_bios: direct-complete suspend
tty ttyS7: direct-complete suspend
tty ttyS6: direct-complete suspend
tty ttyS5: direct-complete suspend
tty ttyS4: direct-complete suspend
tty ttyS3: direct-complete suspend
serial8250 serial8250: bus suspend
tty ptmx: direct-complete suspend
dma dma0chan38: direct-complete suspend
dma dma0chan37: direct-complete suspend
dma dma0chan36: direct-complete suspend
dma dma0chan35: direct-complete suspend
dma dma0chan34: direct-complete suspend
dma dma0chan33: direct-complete suspend
dma dma0chan32: direct-complete suspend
dma dma0chan31: direct-complete suspend
dma dma0chan30: direct-complete suspend
dma dma0chan29: direct-complete suspend
dma dma0chan28: direct-complete suspend
dma dma0chan27: direct-complete suspend
dma dma0chan26: direct-complete suspend
dma dma0chan25: direct-complete suspend
dma dma0chan24: direct-complete suspend
dma dma0chan23: direct-complete suspend
dma dma0chan22: direct-complete suspend
dma dma0chan21: direct-complete suspend
dma dma0chan20: direct-complete suspend
dma dma0chan19: direct-complete suspend
dma dma0chan18: direct-complete suspend
dma dma0chan17: direct-complete suspend
dma dma0chan16: direct-complete suspend
dma dma0chan15: direct-complete suspend
dma dma0chan14: direct-complete suspend
dma dma0chan13: direct-complete suspend
dma dma0chan12: direct-complete suspend
dma dma0chan11: direct-complete suspend
dma dma0chan10: direct-complete suspend
dma dma0chan9: direct-complete suspend
dma dma0chan8: direct-complete suspend
dma dma0chan7: direct-complete suspend
dma dma0chan6: direct-complete suspend
dma dma0chan5: direct-complete suspend
dma dma0chan4: direct-complete suspend
dma dma0chan3: direct-complete suspend
dma dma0chan2: direct-complete suspend
dma dma0chan1: direct-complete suspend
dma dma0chan0: direct-complete suspend
misc autofs: direct-complete suspend
event_source software: direct-complete suspend
event_source breakpoint: direct-complete suspend
clockevents broadcast: direct-complete suspend
clockevents clockevent7: direct-complete suspend
clockevents clockevent6: direct-complete suspend
clockevents clockevent5: direct-complete suspend
clockevents clockevent4: direct-complete suspend
clockevents clockevent3: direct-complete suspend
clockevents clockevent2: direct-complete suspend
clockevents clockevent1: direct-complete suspend
clockevents clockevent0: direct-complete suspend
clockevents: direct-complete suspend
alarmtimer alarmtimer: bus suspend
clocksource clocksource0: direct-complete suspend
clocksource: direct-complete suspend
platform regulatory.0: bus suspend
thermal thermal_zone2: direct-complete suspend
thermal thermal_zone1: direct-complete suspend
thermal thermal_zone0: direct-complete suspend
tty tty63: direct-complete suspend
tty tty62: direct-complete suspend
tty tty61: direct-complete suspend
tty tty60: direct-complete suspend
tty tty59: direct-complete suspend
tty tty58: direct-complete suspend
tty tty57: direct-complete suspend
tty tty56: direct-complete suspend
tty tty55: direct-complete suspend
tty tty54: direct-complete suspend
tty tty53: direct-complete suspend
tty tty52: direct-complete suspend
tty tty51: direct-complete suspend
tty tty50: direct-complete suspend
tty tty49: direct-complete suspend
tty tty48: direct-complete suspend
tty tty47: direct-complete suspend
tty tty46: direct-complete suspend
tty tty45: direct-complete suspend
tty tty44: direct-complete suspend
tty tty43: direct-complete suspend
tty tty42: direct-complete suspend
tty tty41: direct-complete suspend
tty tty40: direct-complete suspend
tty tty39: direct-complete suspend
tty tty38: direct-complete suspend
tty tty37: direct-complete suspend
tty tty36: direct-complete suspend
tty tty35: direct-complete suspend
tty tty34: direct-complete suspend
tty tty33: direct-complete suspend
tty tty32: direct-complete suspend
tty tty31: direct-complete suspend
tty tty30: direct-complete suspend
tty tty29: direct-complete suspend
tty tty28: direct-complete suspend
tty tty27: direct-complete suspend
tty tty26: direct-complete suspend
tty tty25: direct-complete suspend
tty tty24: direct-complete suspend
tty tty23: direct-complete suspend
tty tty22: direct-complete suspend
tty tty21: direct-complete suspend
tty tty20: direct-complete suspend
tty tty19: direct-complete suspend
tty tty18: direct-complete suspend
tty tty17: direct-complete suspend
tty tty16: direct-complete suspend
tty tty15: direct-complete suspend
tty tty14: direct-complete suspend
tty tty13: direct-complete suspend
tty tty12: direct-complete suspend
tty tty11: direct-complete suspend
tty tty10: direct-complete suspend
tty tty9: direct-complete suspend
tty tty8: direct-complete suspend
tty tty7: direct-complete suspend
tty tty6: direct-complete suspend
tty tty5: direct-complete suspend
tty tty4: direct-complete suspend
tty tty3: direct-complete suspend
tty tty2: direct-complete suspend
tty tty1: direct-complete suspend
vc vcsa1: direct-complete suspend
vc vcsu1: direct-complete suspend
vc vcs1: direct-complete suspend
vc vcsa: direct-complete suspend
vc vcsu: direct-complete suspend
vc vcs: direct-complete suspend
tty tty0: direct-complete suspend
tty console: direct-complete suspend
tty tty: direct-complete suspend
mem kmsg: direct-complete suspend
mem urandom: direct-complete suspend
mem random: direct-complete suspend
mem full: direct-complete suspend
mem zero: direct-complete suspend
mem null: direct-complete suspend
mem kmem: direct-complete suspend
mem mem: direct-complete suspend
misc rfkill: direct-complete suspend
net lo: direct-complete suspend
regulator regulator.2: class suspend
regulator regulator.1: class suspend
graphics fbcon: direct-complete suspend
workqueue blkcg_punt_bio: direct-complete suspend
workqueue writeback: direct-complete suspend
cpu cpu7: direct-complete suspend
cpu cpu6: direct-complete suspend
cpu cpu5: direct-complete suspend
cpu cpu3: direct-complete suspend
cpu cpu2: direct-complete suspend
cpu cpu1: direct-complete suspend
tbs_a711 modem: bus suspend
reg-fixed-voltage reg-vbat: bus suspend
reg-fixed-voltage reg-gps: bus suspend
platform 1f01c00.r_cpucfg: bus suspend
platform 1ef0000.hdmi-phy: bus suspend
sun6i-csi 1cb0000.camera: bus suspend
sunxi-wdt 1c20ca0.watchdog: bus suspend
platform 1c20c00.timer: bus suspend
sun8i-a83t-ccu 1c20000.clock: bus suspend
sun6i-msgbox 1c17000.mailbox: bus suspend
eeprom-sunxi-sid 1c14000.eeprom: bus suspend
sun4i-tcon 1c0d000.lcd-controller: bus suspend
sun6i-dma 1c02000.dma-controller: bus suspend
platform 1c0e000.video-codec: bus suspend
platform 1c00000.syscon: bus suspend
ARM-CCI PMU 1799000.pmu: bus suspend
platform 1795000.slave-if: bus suspend
platform 1794000.slave-if: bus suspend
ARM-CCI 1790000.cci: bus suspend
platform 1700000.cpucfg: bus suspend
sun8i-mixer 1200000.mixer: bus suspend
sun8i-mixer 1100000.mixer: bus suspend
sunxi-de2-clks 1000000.clock: bus suspend
sun4i-i2s 1c23000.dai: bus suspend
platform soc: bus suspend
platform scpi: bus suspend
sun4i-drm display-engine: bus suspend
platform timer: bus suspend
vtconsole vtcon0: direct-complete suspend
regulator regulator.0: class suspend
reg-dummy reg-dummy: bus suspend
workqueue: direct-complete suspend
container: direct-complete suspend
cpu: direct-complete suspend[... here it sleeps ...]
axp20x-pek axp221-pek: noirq driver resume
eg-dummy reg-dummy: bus resume
regulator regulator.0: class resume
platform timer: bus resume
sun4i-drm display-engine: bus resume
platform scpi: bus resume
platform soc: bus resume
sun4i-i2s 1c23000.dai: bus resume
ehci-platform 1c1a000.usb: bus resume
ehci-platform 1c1b000.usb: bus resume
usb usb1:type resume
sunxi-de2-clks 1000000.clock: bus resume
usb usb2:type resume
sun8i-mixer 1100000.mixer: bus resume
sun8i-mixer 1200000.mixer: bus resume
platform 1700000.cpucfg: bus resume
ARM-CCI 1790000.cci: bus resume
platform 1794000.slave-if: bus resume
platform 1795000.slave-if: bus resume
ARM-CCI PMU 1799000.pmu: bus resume
platform 1c00000.syscon: bus resume
platform 1c0e000.video-codec: bus resume
sun6i-dma 1c02000.dma-controller: bus resume
sun4i-tcon 1c0d000.lcd-controller: bus resume
eeprom-sunxi-sid 1c14000.eeprom: bus resume
sun6i-msgbox 1c17000.mailbox: bus resume
sun8i-a83t-ccu 1c20000.clock: bus resume
platform 1c20c00.timer: bus resume
sunxi-wdt 1c20ca0.watchdog: bus resume
sun6i-csi 1cb0000.camera: bus resume
platform 1ef0000.hdmi-phy: bus resume
platform 1f01c00.r_cpucfg: bus resume
reg-fixed-voltage reg-gps: bus resume
reg-fixed-voltage reg-vbat: bus resume
tbs_a711 modem: bus resume
regulator regulator.1: class resume
regulator regulator.2: class resume
platform regulatory.0: bus resume
alarmtimer alarmtimer: bus resume
serial8250 serial8250: bus resume
sun8i-a83t-r-pinctrl 1f02c00.pinctrl: bus resume
reg-fixed-voltage reg-vmain: bus resume
regulator regulator.3: class resume
usb 1-1:type resume
sunxi-rsb 1f03400.rsb: bus resume
axp20x-pek axp221-pek: bus resume
input input0:type resume
axp20x-regulator axp20x-regulator: bus resume
regulator regulator.4: class resume
regulator regulator.5: class resume
regulator regulator.6: class resume
regulator regulator.7: class resume
regulator regulator.8: class resume
regulator regulator.9: class resume
regulator regulator.10: class resume
regulator regulator.11: class resume
regulator regulator.12: class resume
regulator regulator.13: class resume
regulator regulator.14: class resume
regulator regulator.15: class resume
regulator regulator.16: class resume
regulator regulator.17: class resume
regulator regulator.18: class resume
regulator regulator.19: class resume
regulator regulator.20: class resume
regulator regulator.21: class resume
regulator regulator.22: class resume
regulator regulator.23: class resume
regulator regulator.24: class resume
regulator regulator.25: class resume
regulator regulator.26: class resume
regulator regulator.27: class resume
usb 1-1:reset high-speed USB device number 2 using ehci-platform
axp20x-gpio axp20x-gpio: bus resume
axp20x-adc axp813-adc: bus resume
axp20x-battery-power-supply axp20x-battery-power-supply: bus resume
platform axp20x-ac-power-supply: bus resume
axp20x-usb-power-supply axp20x-usb-power-supply: bus resume
reg-userspace-consumer reg-userspace-consumer: bus resume
axp20x-leds axp20x-leds: bus resume
leds chgled: class resume
ac100-codec ac100-codec: bus resume
ac100-rtc ac100-rtc: bus resume
rtc rtc0: class resume
sun4i-pwm 1c21400.pwm: bus resume
sun8i-a83t-pinctrl 1c20800.pinctrl: bus resume
pwm pwmchip0: class resume
pwm-backlight backlight: bus resume
backlight backlight: class resume
dw-apb-uart 1c28000.serial: bus resume
dw-apb-uart 1c28400.serial: bus resume
dw-apb-uart 1c28800.serial: bus resume
panel-lvds panel: bus resume
sun4i-a10-lradc-keys 1f03c00.lradc: bus resume
input input1:type resume
mv64xxx_i2c 1c2ac00.i2c: bus resume
mv64xxx_i2c 1c2b000.i2c: bus resume
edt_ft5x06 0-0038: driver resume
bma180 1-0018: driver resume
sun8i-thermal 1f04000.ths: bus resume
cpufreq-dt cpufreq-dt: bus resume
pwrseq_simple wifi_pwrseq: bus resume
sunxi-mmc 1c0f000.mmc: bus resume
sunxi-mmc 1c10000.mmc: bus resume
mmcblk mmc0:aaaa: bus resume
sunxi-mmc 1c11000.mmc: bus resume
mmc mmc1:0001: bus resume
brcmfmac mmc1:0001:1: bus resume
brcmfmac mmc1:0001:2: bus resume
ieee80211 phy0: class resume
leds-gpio leds: bus resume
mmcblk mmc2:0001: bus resume
leds flash: class resume
i2c-gpio i2c-gpio: bus resume
sun4i-usb-phy 1c19400.phy: bus resume
sun4i-tcon 1c0c000.lcd-controller: bus resume
input input2:type resume
rfkill rfkill0: class resume
musb-sunxi 1c19000.usb: bus resume
usb_phy_generic usb_phy_generic.0.auto: bus resume
musb-hdrc musb-hdrc.1.auto: bus resume
musb-sunxi 1c19000.usb: Error unknown writeb offset 112
usb usb3:type resume
hci_uart_bcm serial0-0: driver resume
snd-soc-dummy snd-soc-dummy: bus resume
rfkill rfkill1: class resume
usb 1-1.3:type resume
usb 1-1.3:reset full-speed USB device number 3 using ehci-platform
usb 1-1.3: completing type resume
usb 1-1: completing type resume
usb usb3: completing type resume
usb usb2: completing type resume
usb usb1: completing type resume
OOM killer enabled.
Restarting tasks ... done.
Suspend to idle (s2idle)
So I had an idea to try suspend to idle, since it's a mode where the kernel
will freeze the userspace and turn off all the SoC blocks it can using the
runtime PM hooks.
It requires to compile the kernel with PM_SLEEP and then running:
# eitherecho s2idle > /sys/power/mem_sleep
echo mem > /sys/power/state
# or justecho freeze > /sys/power/state
Though the wakeup is not pretty, and ends up with a huge flood of messages
from 1c10000.mmc, which is mmc1 interface where the
WiFi SDIO is connected to.
There's also a loss of communication with the BT chip. Error
-110 means ETIMEDOUT.
I checked the power draw during suspend to idle with a multimeter, and while
the power draw drops a bit, it's not significant and is probably just a result
of shutting down a bunch of devices, that can also be shut down at runtime.
Also s2idle will seemingly not shut down some devices, because there's quite
a large difference in current draw between doing:
echo1> /sys/class/graphics/fb0/blank
and not doing it before s2idle. The screen goes blank in both cases, but
current draw is much higher during s2idle without blanking the fb0 first.
The reason for failure to communicate with WiFi/BT after wakeup is unclear.
mmc0 works after wakeup, and system can be powered off. It's possible that what
is done with devices during system freeze/sleep states needs to be configured
somewhere first.
Potentially dangerous
powerdown issue
Also I noticed one other strange thing. Shutting down the system from Linux
with powerdown command leaves the tablet still drawing 6mA from the
battery. That will easily kill the battery in around 40–50 days if left
unchecked.
Interestingly powering up the tablet to my u-boot boot menu, and using u-boot
based poweroff drops this current to ~600uA, which allows for more tablet
storage time without use.
Something probably stays on after Linux issues poweroff, and u-boot clears
that up. It can be anything. Linux doesn't really do any deinitialization of HW
on poweroff. It just sends a poweroff command to AXP813.
Audio codec AC100 on A83T
I've been trying to implement some basic audio playback using
this codec.
My general approach is to make the SoC generate all the clocks and be the
clock master. This way, no PLL setup is needed on AC100 chip. That eliminated
one source of complexity.
Other simplification is to keep all DAPs (digital audio processing units)
turned off. They are not necessary for volume control or playback.
The next simplification I wanted to use was to just add the controls
necessary just for the playback path and ignore the rest. It turns out, that
there are like 7 elements in the path, so this approach doesn't save much,
because most of the things in the audio signal path need to be configured
properly anyway.
I find writing SoC audio codec drivers quite error prone. A lot depends on
string matching that C compiler will not check for me, there's a lot of
duplication of identifiers, references between structs with long names, register
references. Writing driver for a chip that has more than a few multiplexers,
mixers and amplifiers, became confusing rather quickly. There's an awful amount
of duplication, variable and register names usually differ just in 1 character
and mentally the code just doesn't map easilly to how the codec is wired up
internally. Information for one widget is spread in multiple structs all over
the file. Not pleasant to work with.
So I changed my approach, and described the codec widgets in my device-tree
like configuration language, which turned out to be rather straightforward, and
I plan to generate most of the hard parts of C code from this description,
while filling the blanks manually.
I've also described AC100
registers in my register description language, and I generate C header file
from that. This also serves as a nice register debugging tool, as seen in this
README.
It turns out, that basic structure of the routes/widgets in the codec can be
fit into less than 300 lines of description. Now I'll just need to add some
metadata to the widget nodes and write a code generator.
UBoot's driver for MMC controller only uses PIO and doesn't try to use DDR
mode when available.
I've implemented DMA access and DDR52 modes. I've also fixed the
bus-width for mmc2 in TBS A711's dts in UBoot, which was still set
to 1, instead of 4. This was already fixed in Linux.
The latest UBoot also contains a regression, where the MMC clock is set
incorrectly, to half the required value, leading to halving the mmc speed on
most of my boards.
This means that my fixes resulted in these nice speedups across all my
boards:
A83T SD card boot – 5MiB/s → 23MiB/s
A83T eMMC boot – 10MiB/s → 47.5MiB/s
H6 SD boot – 10MiB/s → 23MiB/s
H5/H3 SD boot – 21MiB/s → 23MiB/s
I've also decompiled UBoot and SPL binaries in IDA Pro and searched for all
calls to udelay/mdelay. This way I've found one 500ms delay that was not needed
at all and was only included because of incorrect #ifdef check for
a feature that was disabled in the config file.
A small demo /
boot times measurement with a camera
All this combined leads to a very nice speedup of boot times from eMMC and SD
card on TSB A711 tablet. :) Previously with 10MiB/s MMC speed and a needless
500ms delay, UBoot took almost an extra 1s during the whole boot time, which was
significant.
On my setup kernel takes 1s to boot and mount rootfs and my userspace starts
init and UI in about 600ms.
So all this optimization reduced the boot time perceptibly.
There's an extra 1s delay after power on that is probably comming from BROM
doing some initialization and loading UBoot SPL from eMMC.
This delay is shorter when booting from SD card, so one more optimization may
be to have a bootloader on SD card and then load the rest from eMMC.
DRM cursor plane
In order for the X server modesetting driver to NOT use a software cursor,
which slows down rendering, and to use a DRM plane, I've created a patch to mark
one of the planes as a cursor plane.
This revealed some issues with converting DRM plane setup into setup of DE2
mixer/blender HW registers plane changes in the current mixer driver.
More details and the patch are in the mailing list.
With these two patches, lima/panfrost drivers work much better on H5 (Orange
Pi PC 2) and H6 (Orange Pi 3), without any stuttering and slowdowns while moving
a crusor. Even without GPU acceleration, moving a mouse now consumes less CPU
(about 10–20% reduction) and scrolling in Firefox is faster (actually
smooth).
Implmeneting
mailbox/SCPI on Linux and crust probing
This is an attempt to implement „First steps“ from the
previous post.
To speed up testing, we can build SCPI protocol driver as a kernel module and
do unload/load cycle to trigger probing and thus communication with crust.
Afterwards, we can just load crust from userspace into SRAM, do the SCPI
driver module reload and see if scpi_init_versions
succeeds to send CMD_SCPI_CAPABILITIES to crust and gets a
response.
Making Linux talk to crust via
SCPI
First, we modify sun8i-a83t.dtsi to enable SCPI protocol driver
and hook it up to mailbox driver and configure it with shared memory in SRAM A2.
We'll use last 0x200 bytes of SRAM A2 for this purpose.
Linux changes are fairly simple. SRAM A2 doesn't require any
configuration/mapping, so we don't need to modify any C code. We can just add
this to A83T's dtsi:
Note: It turns out, that crust supports two SCPI clients
(client 0, uses message box channels 0 and 1, client 1 uses channels 2 and
3). Client 0 is meant to be ATF and client 1 should be Linux. We'll link the
Linux to the channel meant for ATF, because it has more privileges. That channel
uses shmem from 0x13e00 to 0x14000 and message box
channel 0 to transmit messages from the client side (that is
Linux).
This verifies that SCPI protocol driver writes to the expected location in
SRAM. Value 2 is a code for CMD_SCPI_CAPABILITIES,
which is a command that SCPI driver uses to probe the SCP initially.
Making crust listen to Linux
over SCPI
To build crust we can use gcc 9.2 cross-compiler built for
or1k target.
Crust doesn't support A83T at the moment, so we need to add some basic
support for this SoC. We'll put our platform's files under
platform/sun8i.
We will be loading crust to SRAM A2 from Linux's userspace. This way, we'll
be able to avoid reboot cycles during testing. Crust has a tool at
tools/load.c that can be used to load the firmware and enable the
SCP. This tool expects:
FIRMWARE_BASE – offset where to place firmware from the
base of SRAM A2
FIRMWARE_SIZE – maximum size of firmware image
Crust firmware binary doesn't include exception vectors.
tools/load.c program calculates and writes exception vectors
dynamically.
For A83T we can use the whole SRAM A2 except the first 0x4000
bytes (16KiB) that are reserved for exception vectors. This means
we will have 0x10000 bytes (64KiB) available for
crust. Our platform/sun8i/include/platform/memory.h will
contain:
We'll copy a bunch of files from
platform/sun50i/include/platform, and inspect them and change the
contents to match the A83T SoC.
Next we can compile crust with:
export PATH="/opt/toolchains/or1k-linux-musl/bin:$PATH"export CROSS_COMPILE=or1k-linux-musl-
rm-rf .build-tbs-a711
make V=1 OBJ=.build-tbs-a711 tbs_a711_defconfig
make V=1 OBJ=.build-tbs-a711
To get load program built for ARM, we need to cross-compile it for ARM.
That's a slightly trickier, since by default this program gets built for the
host machine.
# for this to work, we need to comment the #include <config.h> line in# tools/load.c first
arm-linux-musleabihf-gcc -static \
-DCONFIG_PLATFORM='"sun8i"' \
-Iinclude/{common,lib}-Iplatform/sun8i/include \
-o load tools/load.c
Now we have enough to try to load crust on the tablet. We can copy
.build-tbs-a711/scp/scp.bin and load to the tablet
and run:
# reset/stop SCP
./load --reset# load firmware and start SCP
./load scp.bin
# load arm_scpi module
rmmod arm_scpi
modprobe arm_scpi
# see the result
dmesg |tail
But first, let's check what crust does and how.
Crust's internals
When crust starts, it will decide if reset vector or exception vector was
used to jump to start function and passes this information to the
main function.
Crust doesn't use exceptions very much (aside from watchdog reset), and all
exceptions are handled efectively by re-entering the main function again. Only
BSS section is cleared in start code, but all statically
initialized global data will keep values from the previous run of the
main function. This may lead to interesting issues, and needs to be
kept in mind while writing crust code.
Crust's stack size is by default 1KiB. We can increase this if necessary,
because A83T is not as limited as other platforms in the usable SRAM space.
Crust communicates with two SCPI clients, and handles one SCPI message at a
time per client. There's no queueing of messages.
A message box message (which is a 32-bit number) that is sent and also
expected is SCPI_VIRTUAL_CHANNEL, which equals to 1.
Other message box messages are rejected.
Crust's main()
Crude overview of what main() function does:
main(0)
console_init(DEV_UART0)
intialize watchdog
initialize msgbox
initialize scpi
enables msgbox channels
sends SCPI_CMD_SCP_READY to SCPI_CLIENT_EL3
loop:
read msgbox (if system is NOT suspended)
check r_intc for wakeup events (if suspended)
scpi_poll
for each client (SCPI_CLIENT_EL3, SCPI_CLIENT_EL2)
process incomming/outgoing messages
calls scpi_handle_cmd
system_state_machine
watchdog_restart
From this, we can identify what SoC blocks will need to be verified for
initial compatibility with A83T.
With this checked, all that's missing is testing out the connection.
Testing and outstanding issues
Sending messages works, and crust responds, but there's an issue where
arm_scpi driver in Linux expects bi-directional message boxes, but
sun6i-msgbox implements unidirectional ones, so
arm_scpi will not see the response, and the probe times out.
One solution may be to switch sun6i-msgbox to use 2 HW channels
and provide a bi-directional interface arm_scpi expects.
The other is to patch the arm_scpi driver to accept separate
message boxes for tx and rx paths and share a single shmem. I've experimentally
patched the arm_scpi driver.
The result is:
scpi_protocol scpi: SCP Protocol 1.2 Firmware 0.1.9000 version
So now we have Linux communicating with the SCP over SCPI.
:)
Current state and next steps
With SCPI interface working, we can try porting MCPM code for A83T to crust
and start experimenting with suspending the CPU cores. Actual porting will
probably not be hard, since the code already exists in css-a64.c.
It will probably only need a few small tweaks.
Big hurdle here is that we will not be able to use PSCI interface and
standard ARM implementation of it, so we'll have to write our own driver that
will replace what it does.
Next step is to investigate what roles PSCI typically plays druring system
power management and how to do without it, and minimally replicate it using
direct communication with SCP from the kernel.
After we do this, we should have a working mechanism for disabling all CPU
cores and wakeup.
Getting
mailbox support up on A83T and testing crust
The main goal is to use crust to get to the lowest suspend state, we can.
That is to turn of all CPUs at least, and power down CPU regulators. If
possible, we can also put DRAM into self-refresh and turn off DRAM controller.
We can potentially play with shutting down other parts of the SoC, like PLLs,
other voltage regulators, etc.
Linux tells ATF over PSCI that it wants to enable/disable CPU cores, or run
system suspend
Currently, suspend works on supproted platforms (A64, H5, …) this way.
For A83T, we don't use either ATF, nor U-Boot to implement PSCI, so Linux
kernel configures CPUs directly in kernel space via arch/arm/mach-sunxi/mc_smp.c.
Overview
of how crust integration should work in the end
We can't use ATF on A83T, so Linux will need to talk directly to SCP using
SCPI. To that end, Linux kernel already has a SCPI
driver, that when enabled and configured to use A83T message box and shared
memory in SRAM A2, will provide struct
scpi_ops via get_scpi_ops() function to anything in the
kernel.
By itself, this will not do anything, except provide a way to communicate
with crust over the standard SCPI interface from any driver inside the kernel.
Linux kernel will not automatically use SCPI interface to manage CPU
hotplug/suspend.
We have to write code that will provide struct
platform_suspend_ops for A83T, and that will actually notify SCP when the
kernel wants the system to enter suspended state. For already supported SoCs
(A64, H5), this is implemented by generic
PSCI driver.
Commands required for CPU/system power management are not currently provided
by struct scpi_ops. We will need to implement hooks for these
commands:
These are not provided yet, because no code in the kernel uses SCPI for
CSS/SYS power state management. Everything is done either via PSCI, or via
custom platform_suspend_ops defined by each platform. We will be
the first to try that.
It's unclear if we should implement PM as A83T specific
platform_suspend_ops or write a PSCI-like generic driver that would
define platform_suspend_ops for all SoCs that include
arm,scpi-pm compatible node in DT, for example. It's probably
safer and easier to start with A83T specific driver.
SCP would normally also implement CPU/cluster power state changes that are
used for CPU hotplug, but that is currently handled directly from kernel space.
Clean solution would be to replace current MCPM A83T code in Linux with SCPI
calls. Though this may not be necessary for initial experiments with suspend to
RAM functionality, especially if CPU hotplug will not be used during suspend.
Precise interactions between CPU hotplug code in kernel and SCP are not
yet clear.
Boot sequence
To get crust running and communicating with the kernel, this needs to
happen:
U-Boot loads crust firmware to SRAM A2 from binary file (via
load mmc 0:1 $SRAM_A2_BASE crust.bin)
U-Boot de-asserts CPUS reset and sets up its clock (I already implemented
a
command to do that.)
U-Boot starts Linux
Linux needs to be set up so that it uses SCPI protocol and message box to
communicate with crust in DT
Linux probes SCPI driver
SCPI driver tries to send SCPI_CMD_SCPI_CAPABILITIES to SCP and probes
successfully if SCP (crust) responds
From now on, we can use get_scpi_ops() to communicate with crust
from anywher inside the kernel.
First steps
Our first milestone is to have the above boot sequence working. This will
require us to:
build crust firmware for A83T
port mailbox code
decide on location of shared mem for SCPI in SRAM A2
figure out how to link crust so that this shmem space is reserved
configure u-boot
to load crust from SD card to the correct location
to deassert SCP's reset and configure its clock (already
done)
modify sun8i-a83t.dtsi
add mailbox node
add sram node for SRAM A2
add scpi node
use mailbox
use shmem area defined inside sram node
enable drivers in kernel config
We'll achieve this milestone if scpi driver probes successfully.
Next steps
copy MCPM code from Linux into crust, and use it to
implement SET/GET_CSS_PWR_STATE
(this will be a failry good test for whether the code works, before going
further into uncharted territory)
implement AXP813 driver inside crust
necessary for turning off CPU power supplies
necessary to get POK interrupt for wakeup?
try to implement SET_SYS_PWR_STATE
Touch control based
tablet boot menu for u-boot
A711 tablet has a nice feature that it can boot either from internal eMMC or
a microSD card, if the inserted card is found to be bootable.
When developing and testing either kernel or various root filesystem variants
it is useful to be able to have multiple boot configurations and to be able to
select one of those easily. That means not via UART based serial console and
u-boot CLI. That's not the best UX for a tablet.
Ideally, I'd like to be able to have a bootable microSD card with u-boot
configured such that:
I can boot a known stable regular Arch Linux distro on eMMC or
SD card
I can choose dev Linux kernel or a known stable one
I can select rootfs variant. I like to experiment with a fully custom and
highly optimized userspace implementations composed of just a few static
binaries and a custom init system. But I also like to be able to quickly switch
to a more fully featured GNU/Linux OS.
Having a boot UI makes all this much less cubersome as it gets away with a
need to switch sd cards, or with a need for an UART cable or other wired
mechanism to control u-boot.
U-boot touch based UI
U-boot has support for framebuffer based video output on a LCD.
What's missing is a support for touch panel based input. That specifically
means u-boot does not have any commands for getting list of touches from a touch
panel controller, that could be used in a boot script to implement some kind of
crude UI.
So in order to get a boot menu in u-boot, we need:
A new command to render the menu options and wait for touch input over the
rendered buttons.
A generic driver model uclass for getting touches in an abstract way.
U-boot's driver model consists of uclasses (generic interfaces for a particular
class of a device, like a display or a touch panel controller).
Driver for the touch panel controller (FT5×06 in case of TBS A711).
Some additional utility commands (a new uclass usually has a command for
enumerating available devices, testing them, etc.)
To modify the DTS/platform data so that our touch panel is registered and
configured correctly.
Touchpanel uclass
U-boot doesn't have interrupts enabled, so the interface to a touch panel can
be as simple as a single function that returns a list of active touches and
their X/Y positions registered by the touch panel controller.
#ifndef __TOUCHPANEL_H#define __TOUCHPANEL_H/** * struct touchpanel_priv - information about a touchpanel, for the uclass * * @sdev: stdio device */struct touchpanel_priv {int size_x;int size_y;};struct touchpanel_touch {int id;int x;int y;};/** * struct touchpanel_ops - touchpanel device operations */struct touchpanel_ops {/** * start() - enable the touchpanel to be ready for use * * @dev: Device to enable * @return 0 if OK, -ve on error */int(*start)(struct udevice *dev);/** * stop() - disable the touchpanel when no-longer needed * * @dev: Device to disable * @return 0 if OK, -ve on error */int(*stop)(struct udevice *dev);/** * get_touches() - get list of active touches * * @dev: Device to read from * @touches: Array where to store touches. If NULL, the driver will * only return number of touches available. * @max_touches: Size of an touches array * @return -EAGAIN if no touch is available, otherwise number of touches * available. */int(*get_touches)(struct udevice *dev,struct touchpanel_touch* touches,int max_touches);};#define touchpanel_get_ops(dev) ((struct touchpanel_ops *)(dev)->driver->ops)inttouchpanel_start(struct udevice *dev);inttouchpanel_stop(struct udevice *dev);inttouchpanel_get_touches(struct udevice *dev,struct touchpanel_touch* touches,int max_touches);#endif/* __TOUCHPANEL_H */
Implmentation of the uclass is therefore quite simple too:
There's no need to reinvent the wheel. We can simply take the Linux driver
and adapt it to u-boot. This means mostly:
dropping interrupt handling
adapting probing code to use u-boot driver model
adapting I2C interface
// SPDX-License-Identifier: GPL-2.0/* * (C) Copyright 2018 Ondrej Jirman <megous@megous.com> * * Based on the Linux driver drivers/input/touchscreen/edt-ft5x06.c (v4.18): * * Copyright (C) 2012 Simon Budig, <simon.budig@kernelconcepts.de> * Daniel Wagener <daniel.wagener@kernelconcepts.de> (M09 firmware support) * Lothar Waßmann <LW@KARO-electronics.de> (DT support) */#include <common.h>#include <dm.h>#include <errno.h>#include <input.h>#include <asm/io.h>#include <asm/gpio.h>#include <command.h>#include <i2c.h>#include <touchpanel.h>#include <power/regulator.h>
DECLARE_GLOBAL_DATA_PTR;#define WORK_REGISTER_THRESHOLD 0x00#define WORK_REGISTER_REPORT_RATE 0x08#define WORK_REGISTER_GAIN 0x30#define WORK_REGISTER_OFFSET 0x31#define WORK_REGISTER_NUM_X 0x33#define WORK_REGISTER_NUM_Y 0x34#define M09_REGISTER_THRESHOLD 0x80#define M09_REGISTER_GAIN 0x92#define M09_REGISTER_OFFSET 0x93#define M09_REGISTER_NUM_X 0x94#define M09_REGISTER_NUM_Y 0x95#define NO_REGISTER 0xff#define WORK_REGISTER_OPMODE 0x3c#define FACTORY_REGISTER_OPMODE 0x01#define TOUCH_EVENT_DOWN 0x00#define TOUCH_EVENT_UP 0x01#define TOUCH_EVENT_ON 0x02#define TOUCH_EVENT_RESERVED 0x03#define EDT_NAME_LEN 23#define EDT_SWITCH_MODE_RETRIES 10#define EDT_SWITCH_MODE_DELAY 5/* msec */#define EDT_RAW_DATA_RETRIES 100#define EDT_RAW_DATA_DELAY 1000/* usec */enum edt_ver {
EDT_M06,
EDT_M09,
EDT_M12,
GENERIC_FT,};struct edt_reg_addr {int reg_threshold;int reg_report_rate;int reg_gain;int reg_offset;int reg_num_x;int reg_num_y;};struct ft5x06_priv {struct udevice *reg;struct gpio_desc reset_gpio;
u16 num_x;
u16 num_y;int threshold;int gain;int offset;int report_rate;int max_support_points;char name[EDT_NAME_LEN];struct edt_reg_addr reg_addr;enum edt_ver version;};static intft5x06_readwrite(struct udevice *dev,
u16 wr_len,void*wr_buf,
u16 rd_len,void*rd_buf){struct dm_i2c_chip *chip =dev_get_parent_platdata(dev);struct i2c_msg wrmsg[2];int ret, i =0;if(wr_len) {
wrmsg[i].addr = chip->chip_addr;
wrmsg[i].flags =0;
wrmsg[i].len = wr_len;
wrmsg[i].buf = wr_buf;
i++;}if(rd_len) {
wrmsg[i].addr = chip->chip_addr;
wrmsg[i].flags = I2C_M_RD;
wrmsg[i].len = rd_len;
wrmsg[i].buf = rd_buf;
i++;}
ret =dm_i2c_xfer(dev, wrmsg, i);if(ret <0)return ret;return0;}static intft5x06_register_write(struct udevice *dev, u8 addr, u8 value){struct ft5x06_priv *priv =dev_get_priv(dev);
u8 wrbuf[4];switch(priv->version) {case EDT_M06:
wrbuf[0] =0xfc;
wrbuf[1] = addr &0x3f;
wrbuf[2] = value;
wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2];returnft5x06_readwrite(dev,4, wrbuf,0, NULL);case EDT_M09:case EDT_M12:case GENERIC_FT:
wrbuf[0] = addr;
wrbuf[1] = value;returnft5x06_readwrite(dev,2, wrbuf,0, NULL);default:return-EINVAL;}}static intft5x06_register_read(struct udevice *dev, u8 addr){struct ft5x06_priv *priv =dev_get_priv(dev);
u8 wrbuf[2], rdbuf[2];int error;switch(priv->version) {case EDT_M06:
wrbuf[0] =0xfc;
wrbuf[1] = addr &0x3f;
wrbuf[1] |=0x40;
error =ft5x06_readwrite(dev,2, wrbuf,2, rdbuf);if(error)return error;if((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) {dev_err(dev,"crc error: 0x%02x expected, got 0x%02x\n",
wrbuf[0] ^ wrbuf[1] ^ rdbuf[0],
rdbuf[1]);return-EIO;}break;case EDT_M09:case EDT_M12:case GENERIC_FT:
wrbuf[0] = addr;
error =ft5x06_readwrite(dev,1, wrbuf,1, rdbuf);if(error)return error;break;default:return-EINVAL;}return rdbuf[0];}static intft5x06_identify(struct udevice *dev,char*fw_version){struct ft5x06_priv *priv =dev_get_priv(dev);
u8 rdbuf[EDT_NAME_LEN];char*p;int error;char*model_name = priv->name;/* see what we find if we assume it is a M06 * * if we get less than EDT_NAME_LEN, we don't want * to have garbage in there */memset(rdbuf,0,sizeof(rdbuf));
error =ft5x06_readwrite(dev,1,"\xBB", EDT_NAME_LEN -1, rdbuf);if(error)return error;/* Probe content for something consistent. * M06 starts with a response byte, M12 gives the data directly. * M09/Generic does not provide model number information. */if(!strncasecmp(rdbuf +1,"EP0",3)) {
priv->version = EDT_M06;/* remove last '$' end marker */
rdbuf[EDT_NAME_LEN -1] ='\0';if(rdbuf[EDT_NAME_LEN -2] =='$')
rdbuf[EDT_NAME_LEN -2] ='\0';/* look for Model/Version separator */
p =strchr(rdbuf,'*');if(p)*p++ ='\0';strlcpy(model_name, rdbuf +1, EDT_NAME_LEN);strlcpy(fw_version, p ? p :"", EDT_NAME_LEN);}else if(!strncasecmp(rdbuf,"EP0",3)) {
priv->version = EDT_M12;/* remove last '$' end marker */
rdbuf[EDT_NAME_LEN -2] ='\0';if(rdbuf[EDT_NAME_LEN -3] =='$')
rdbuf[EDT_NAME_LEN -3] ='\0';/* look for Model/Version separator */
p =strchr(rdbuf,'*');if(p)*p++ ='\0';strlcpy(model_name, rdbuf, EDT_NAME_LEN);strlcpy(fw_version, p ? p :"", EDT_NAME_LEN);}else{/* If it is not an EDT M06/M12 touchscreen, then the model * detection is a bit hairy. The different ft5x06 * firmares around don't reliably implement the * identification registers. Well, we'll take a shot. * * The main difference between generic focaltec based * touches and EDT M09 is that we know how to retrieve * the max coordinates for the latter. */
priv->version = GENERIC_FT;
error =ft5x06_readwrite(dev,1,"\xA6",2, rdbuf);if(error)return error;strlcpy(fw_version, rdbuf,2);
error =ft5x06_readwrite(dev,1,"\xA8",1, rdbuf);if(error)return error;/* This "model identification" is not exact. Unfortunately * not all firmwares for the ft5x06 put useful values in * the identification registers. */switch(rdbuf[0]) {case0x35:/* EDT EP0350M09 */case0x43:/* EDT EP0430M09 */case0x50:/* EDT EP0500M09 */case0x57:/* EDT EP0570M09 */case0x70:/* EDT EP0700M09 */
priv->version = EDT_M09;snprintf(model_name, EDT_NAME_LEN,"EP0%i%i0M09",
rdbuf[0] >>4, rdbuf[0] &0x0F);break;case0xa1:/* EDT EP1010ML00 */
priv->version = EDT_M09;snprintf(model_name, EDT_NAME_LEN,"EP%i%i0ML00",
rdbuf[0] >>4, rdbuf[0] &0x0F);break;case0x5a:/* Solomon Goldentek Display */snprintf(model_name, EDT_NAME_LEN,"GKTW50SCED1R0");break;default:snprintf(model_name, EDT_NAME_LEN,"generic ft5x06 (%02x)",
rdbuf[0]);break;}}return0;}static voidft5x06_get_defaults(struct udevice *dev){//XXX: not supported yet, should be read from DT#if 0struct ft5x06_priv *priv =dev_get_priv(dev);struct edt_reg_addr *reg_addr = &priv->reg_addr;
u32 val;int error;
error =device_property_read_u32(dev,"threshold", &val);if(!error) {ft5x06_register_write(dev, reg_addr->reg_threshold, val);
priv->threshold = val;}
error =device_property_read_u32(dev,"gain", &val);if(!error) {ft5x06_register_write(dev, reg_addr->reg_gain, val);
priv->gain = val;}
error =device_property_read_u32(dev,"offset", &val);if(!error) {ft5x06_register_write(dev, reg_addr->reg_offset, val);
priv->offset = val;}#endif}static voidft5x06_get_parameters(struct udevice *dev){struct ft5x06_priv *priv =dev_get_priv(dev);struct edt_reg_addr *reg_addr = &priv->reg_addr;
priv->threshold =ft5x06_register_read(dev, reg_addr->reg_threshold);
priv->gain =ft5x06_register_read(dev, reg_addr->reg_gain);
priv->offset =ft5x06_register_read(dev, reg_addr->reg_offset);if(reg_addr->reg_report_rate != NO_REGISTER)
priv->report_rate =ft5x06_register_read(dev,
reg_addr->reg_report_rate);if(priv->version == EDT_M06 ||
priv->version == EDT_M09 ||
priv->version == EDT_M12) {
priv->num_x =ft5x06_register_read(dev, reg_addr->reg_num_x);
priv->num_y =ft5x06_register_read(dev, reg_addr->reg_num_y);}else{
priv->num_x = -1;
priv->num_y = -1;}}static voidft5x06_set_regs(struct udevice *dev){struct ft5x06_priv *priv =dev_get_priv(dev);struct edt_reg_addr *reg_addr = &priv->reg_addr;switch(priv->version) {case EDT_M06:
reg_addr->reg_threshold = WORK_REGISTER_THRESHOLD;
reg_addr->reg_report_rate = WORK_REGISTER_REPORT_RATE;
reg_addr->reg_gain = WORK_REGISTER_GAIN;
reg_addr->reg_offset = WORK_REGISTER_OFFSET;
reg_addr->reg_num_x = WORK_REGISTER_NUM_X;
reg_addr->reg_num_y = WORK_REGISTER_NUM_Y;break;case EDT_M09:case EDT_M12:
reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
reg_addr->reg_report_rate = NO_REGISTER;
reg_addr->reg_gain = M09_REGISTER_GAIN;
reg_addr->reg_offset = M09_REGISTER_OFFSET;
reg_addr->reg_num_x = M09_REGISTER_NUM_X;
reg_addr->reg_num_y = M09_REGISTER_NUM_Y;break;case GENERIC_FT:/* this is a guesswork */
reg_addr->reg_threshold = M09_REGISTER_THRESHOLD;
reg_addr->reg_gain = M09_REGISTER_GAIN;
reg_addr->reg_offset = M09_REGISTER_OFFSET;break;}}static boolft5x06_check_crc(struct udevice *dev, u8 *buf,int buflen){int i;
u8 crc =0;for(i =0; i < buflen -1; i++)
crc ^= buf[i];if(crc != buf[buflen-1]) {dev_err(dev,"crc error: 0x%02x expected, got 0x%02x\n",
crc, buf[buflen-1]);return false;}return true;}static intft5x06_get_touches(struct udevice* dev,struct touchpanel_touch* touches,int max_touches){struct ft5x06_priv *priv =dev_get_priv(dev);
u8 cmd;
u8 rdbuf[63];int i, type, x, y, id;int offset, tplen, datalen, crclen;int error;int touches_count =0;switch(priv->version) {case EDT_M06:
cmd =0xf9;/* tell the controller to send touch data */
offset =5;/* where the actual touch data starts */
tplen =4;/* data comes in so called frames */
crclen =1;/* length of the crc data */break;case EDT_M09:case EDT_M12:case GENERIC_FT:
cmd =0x0;
offset =3;
tplen =6;
crclen =0;break;default:goto out;}memset(rdbuf,0,sizeof(rdbuf));
datalen = tplen * priv->max_support_points + offset + crclen;
error =ft5x06_readwrite(dev,sizeof(cmd), &cmd, datalen, rdbuf);if(error) {dev_err(dev,"Unable to fetch data, error: %d\n", error);goto out;}/* M09/M12 does not send header or CRC */if(priv->version == EDT_M06) {if(rdbuf[0] !=0xaa|| rdbuf[1] !=0xaa||
rdbuf[2] != datalen) {dev_err(dev,"Unexpected header: %02x%02x%02x!\n",
rdbuf[0], rdbuf[1], rdbuf[2]);goto out;}if(!ft5x06_check_crc(dev, rdbuf, datalen))goto out;}for(i =0; i < priv->max_support_points; i++) {
u8 *buf = &rdbuf[i * tplen + offset];bool down;
type = buf[0] >>6;/* ignore Reserved events */if(type == TOUCH_EVENT_RESERVED)continue;/* M06 sometimes sends bogus coordinates in TOUCH_DOWN */if(priv->version == EDT_M06 && type == TOUCH_EVENT_DOWN)continue;
x = ((buf[0] <<8) | buf[1]) &0x0fff;
y = ((buf[2] <<8) | buf[3]) &0x0fff;
id = (buf[2] >>4) &0x0f;
down = type != TOUCH_EVENT_UP;if(!down)continue;if(max_touches > touches_count) {
touches[touches_count].x = x;
touches[touches_count].y = y;
touches[touches_count].id = id;
touches_count++;}}
out:return touches_count;}static intft5x06_start(struct udevice *dev){debug("%s: started\n", __func__);return0;}static intft5x06_stop(struct udevice *dev){debug("%s: stopped\n", __func__);return0;}/** * Set up the touch panel. * * @return 0 if ok, -ERRNO on error */static intft5x06_probe(struct udevice *dev){struct touchpanel_priv *uc_priv =dev_get_uclass_priv(dev);struct ft5x06_priv *priv =dev_get_priv(dev);int ret;
priv->max_support_points =5;if(priv->reg &&CONFIG_IS_ENABLED(DM_REGULATOR)) {
ret =regulator_set_enable(priv->reg,true);if(ret) {debug("%s: Cannot enable regulator for touchpanel '%s'\n",
__func__, dev->name);return ret;}udelay(20*1000);}if(dm_gpio_is_valid(&priv->reset_gpio)) {
ret =dm_gpio_set_value(&priv->reset_gpio,0);if(ret)return ret;}udelay(300*1000);char fw_version[EDT_NAME_LEN];
ret =ft5x06_identify(dev, fw_version);if(ret) {dev_err(dev,"touchscreen probe failed %d\n", ret);return ret;}ft5x06_set_regs(dev);ft5x06_get_defaults(dev);ft5x06_get_parameters(dev);debug("Model\"%s\", Rev.\"%s\", %dx%d sensors\n",
priv->name, fw_version, priv->num_x, priv->num_y);if(priv->version == EDT_M06 ||
priv->version == EDT_M09 ||
priv->version == EDT_M12) {
uc_priv->size_x = priv->num_x *64;
uc_priv->size_y = priv->num_y *64;}else{//XXX: perhaps check that the user set the values in DT}debug("%s: ready\n", __func__);return0;}static intft5x06_ofdata_to_platdata(struct udevice *dev){struct ft5x06_priv *priv =dev_get_priv(dev);int ret;debug("%s: start\n", __func__);
ret =uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,"power-supply", &priv->reg);if(ret) {debug("%s: Cannot get power supply: ret=%d\n", __func__, ret);if(ret != -ENOENT)return ret;}
ret =gpio_request_by_name(dev,"reset-gpios",0, &priv->reset_gpio,
GPIOD_IS_OUT);if(ret) {debug("%s: Warning: cannot get enable GPIO: ret=%d\n",
__func__, ret);if(ret != -ENOENT)return ret;}debug("%s: done\n", __func__);return0;}static const struct touchpanel_ops ft5x06_ops = {.start = ft5x06_start,.stop = ft5x06_stop,.get_touches = ft5x06_get_touches,};static const struct udevice_id ft5x06_ids[] = {{ .compatible ="edt,edt-ft5x06"},{ }};U_BOOT_DRIVER(ft5x06) = {.name ="touchpanel-ft5x06",.id = UCLASS_TOUCHPANEL,.of_match = ft5x06_ids,.probe = ft5x06_probe,.ops = &ft5x06_ops,.ofdata_to_platdata = ft5x06_ofdata_to_platdata,.priv_auto_alloc_size =sizeof(struct ft5x06_priv),};
Touch utility command
U-boot has a CLI and scripting interface that can run commands. These
commands are C functions that can be registered with u-boot and utilize
u-boot's driver model (via uclass interfaces) to perform their, well…
function.
So here we create a command that can probe the touch panel device and query
the basic information about it (touchpanel size and the list of touches).
// SPDX-License-Identifier: GPL-2.0+/* * Copyright (C) 2018 BayLibre, SAS * Author: Neil Armstrong <narmstrong@baylibre.com> */#include <common.h>#include <command.h>#include <dm.h>#include <touchpanel.h>static intdo_touch_list(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){struct udevice *dev;int ret;
ret =uclass_first_device_err(UCLASS_TOUCHPANEL, &dev);if(ret) {printf("No available touchpanel device\n");return CMD_RET_FAILURE;}do{printf("- %s\n", dev->name);
ret =uclass_next_device(&dev);if(ret)return CMD_RET_FAILURE;}while(dev);return CMD_RET_SUCCESS;}static intdo_touch_info(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){struct touchpanel_priv *uc_priv;struct udevice *dev;int ret;if(argc <2)return CMD_RET_USAGE;
ret =uclass_get_device_by_name(UCLASS_TOUCHPANEL, argv[1], &dev);if(ret) {printf("Unknown touchpanel device %s\n", argv[1]);return CMD_RET_FAILURE;}
uc_priv =dev_get_uclass_priv(dev);printf("Touchpanel Device '%s' :\n", argv[1]);printf("size_x: %d\n", uc_priv->size_x);printf("size_y: %d\n", uc_priv->size_y);return CMD_RET_SUCCESS;}static intdo_touch_get(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){//struct touchpanel_priv *uc_priv;struct touchpanel_touch touches[10];struct udevice *dev;int ret, i;if(argc <2)return CMD_RET_USAGE;
ret =uclass_get_device_by_name(UCLASS_TOUCHPANEL, argv[1], &dev);if(ret) {printf("Unknown touchpanel device %s\n", argv[1]);return CMD_RET_FAILURE;}//uc_priv = dev_get_uclass_priv(dev);printf("Touchpanel Device '%s' :\n", argv[1]);
ret =touchpanel_start(dev);if(ret <0) {printf("Failed to start %s, err=%d\n", argv[1], ret);return CMD_RET_FAILURE;}
ret =touchpanel_get_touches(dev, touches,ARRAY_SIZE(touches));if(ret <0) {printf("Failed to get touches from %s, err=%d\n", argv[1], ret);return CMD_RET_FAILURE;}for(i =0; i < ret; i++) {printf("touch: id=%d x=%d y=%d\n", touches[i].id, touches[i].x,
touches[i].y);}
ret =touchpanel_stop(dev);if(ret <0) {printf("Failed to stop %s, err=%d\n", argv[1], ret);return CMD_RET_FAILURE;}return CMD_RET_SUCCESS;}static cmd_tbl_t cmd_touch_sub[] = {U_BOOT_CMD_MKENT(list,1,1, do_touch_list,"",""),U_BOOT_CMD_MKENT(info,2,1, do_touch_info,"",""),U_BOOT_CMD_MKENT(get,2,1, do_touch_get,"",""),};static intdo_touch(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){
cmd_tbl_t *c;if(argc <2)return CMD_RET_USAGE;/* Strip off leading 'touch' command argument */
argc--;
argv++;
c =find_cmd_tbl(argv[0], &cmd_touch_sub[0],ARRAY_SIZE(cmd_touch_sub));if(c)return c->cmd(cmdtp, flag, argc, argv);elsereturn CMD_RET_USAGE;}static char touch_help_text[] ="list - list touchpanel devices\n""touch info <name> - Get touchpanel device info\n""touch get <name> - Get touches";U_BOOT_CMD(touch,4,1, do_touch,"Touchpanel sub-system", touch_help_text);
This code also doubles as a compact example of the use of touchpanel uclass
itnerface.
Boot menu UI drawing and
touch handling
The last step is to create a command that can draw the boot menu UI over the
framebuffer and waits for the touch over the active areas (buttons).
To be somewhat configurable without the need to recompile u-boot, the command
takes as parameters the list of boot menu options. On exit it returns the
selected option as a exit status code.
// SPDX-License-Identifier: GPL-2.0+/* * Copyright (C) 2018 Ondrej Jirman <megous@megous.com> */#include <common.h>#include <command.h>#include <cli_hush.h>#include <video.h>#include <video_font.h>#include <dm/uclass.h>#include <dm/device.h>#include <touchpanel.h>// first some generic drawing primitivesstruct painter {
u8* fb;
u8* fb_end;
u8* cur;
u32 line_length;
u32 bpp;
u32 rows;
u32 cols;};static voidpainter_set_xy(struct painter* p, uint x, uint y){
p->cur = p->fb +min(y, p->rows -1) * p->line_length +min(x, p->cols -1) * p->bpp;}static voidpainter_move_dxy(struct painter* p,int dx,int dy){
p->cur += dy * p->line_length + dx * p->bpp;if(p->cur >= p->fb_end)
p->cur = p->fb_end -1;if(p->cur < p->fb)
p->cur = p->fb;}static voidpainter_rect_fill(struct painter* p, uint w, uint h, u32 color){int x, y;
u32* cur;for(y =0; y < h; y++) {
cur = (u32*)(p->cur + p->line_length * y);for(x =0; x < w; x++)*(cur++) = color;}}static voidpainter_line_h(struct painter* p,int dx, u32 color){if(dx <0) {painter_move_dxy(p,0, dx);painter_rect_fill(p,1, -dx, color);}else{painter_rect_fill(p,1, dx, color);painter_move_dxy(p,0, dx);}}static voidpainter_line_v(struct painter* p,int dy, u32 color){if(dy <0) {painter_move_dxy(p,0, dy);painter_rect_fill(p,1, -dy, color);}else{painter_rect_fill(p,1, dy, color);painter_move_dxy(p,0, dy);}}static voidpainter_bigchar(struct painter* p,char ch, u32 color){int i, row;void*line = p->cur;for(row =0; row < VIDEO_FONT_HEIGHT *2; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row /2];uint32_t*dst = line;for(i =0; i < VIDEO_FONT_WIDTH; i++) {if(bits &0x80) {*dst = color;*(dst+1) = color;}
bits <<=1;
dst+=2;}
line += p->line_length;}painter_move_dxy(p, VIDEO_FONT_WIDTH *2,0);}static voidpainter_char(struct painter* p,char ch, u32 color){int i, row;void*line = p->cur;for(row =0; row < VIDEO_FONT_HEIGHT; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];uint32_t*dst = line;for(i =0; i < VIDEO_FONT_WIDTH; i++) {if(bits &0x80)*dst = color;
bits <<=1;
dst++;}
line += p->line_length;}painter_move_dxy(p, VIDEO_FONT_WIDTH,0);}// menu commandstatic inthandle_tmenu(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[],int no_touch){struct udevice *vdev, *tdev;struct video_priv *vpriv;struct touchpanel_touch touches[10];int ret, i, row, j;if(argc <2) {return CMD_RET_USAGE;}// set some params: (parse from argv in the future)char*const* items = argv +1;int items_count = argc -1;int w =350, h =120, x =30, y = (600- (h +10) * items_count -10) /2;
ret =uclass_first_device_err(UCLASS_VIDEO, &vdev);if(ret)return CMD_RET_FAILURE;if(!no_touch) {
ret =uclass_first_device_err(UCLASS_TOUCHPANEL, &tdev);if(ret)return CMD_RET_FAILURE;}
vpriv =dev_get_uclass_priv(vdev);if(vpriv->bpix != VIDEO_BPP32) {printf("tmenu requires 32BPP video device\n");return CMD_RET_FAILURE;}struct painter p = {.fb = vpriv->fb,.fb_end = vpriv->fb + vpriv->fb_size,.cur = vpriv->fb,.line_length = vpriv->line_length,.bpp =VNBYTES(vpriv->bpix),.cols = vpriv->xsize,.rows = vpriv->ysize,};int selected = -1;int redraw =1;if(!no_touch) {
ret =touchpanel_start(tdev);if(ret <0) {printf("Failed to start %s, err=%d\n", tdev->name, ret);return CMD_RET_FAILURE;}}
next:while(1) {if(redraw) {// redraw output
row = y;for(i =0; i < items_count; i++) {painter_set_xy(&p, x, row);painter_rect_fill(&p, w, h, i == selected ?0xff555500:0xff005500);painter_set_xy(&p,
x + (w -strlen(items[i]) * VIDEO_FONT_WIDTH *2) /2,
row + (h - VIDEO_FONT_HEIGHT *2) /2);for(j =0; items[i][j]; j++)painter_bigchar(&p, items[i][j],0xffffffff);
row += h +10;}video_sync(vdev,true);
redraw =0;}if(no_touch)return CMD_RET_SUCCESS;// don't be too busy reading i2cudelay(50*1000);// handle input
ret =touchpanel_get_touches(tdev, touches,ARRAY_SIZE(touches));if(ret <0) {printf("Failed to get touches from %s, err=%d\n", tdev->name, ret);return CMD_RET_FAILURE;}for(i =0; i < ret; i++) {int tx = touches[i].x;int ty = touches[i].y;if(tx < x || tx > x + w)continue;
row = y;for(j =0; j < items_count; j++) {if(ty > row && ty < row + h) {// got matchif(selected != j) {
selected = j;
redraw =1;}goto next;}
row += h +10;}}if(selected != -1) {// we are donechar buf[16];snprintf(buf,sizeof buf,"ret=%d", selected);set_local_var(buf,1);
selected = -1;
redraw =1;break;}}
ret =touchpanel_stop(tdev);if(ret <0) {printf("Failed to stop %s, err=%d\n", tdev->name, ret);return CMD_RET_FAILURE;}return CMD_RET_SUCCESS;}static intdo_tmenu_render(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){returnhandle_tmenu(cmdtp, flag, argc, argv,1);}static intdo_tmenu_input(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){returnhandle_tmenu(cmdtp, flag, argc, argv,0);}static intdo_tmenu(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[]){int ret;
ret =do_tmenu_render(cmdtp, flag, argc, argv);if(ret == CMD_RET_SUCCESS)
ret =do_tmenu_input(cmdtp, flag, argc, argv);return ret;}U_BOOT_CMD(tmenu,8,1, do_tmenu,"tmenu","tmenu item1 [item2...] - show touch menu and wait for input");U_BOOT_CMD(tmenu_render,8,1, do_tmenu_render,"tmenu_render","tmenu_render item1 [item2...] - show touch menu");U_BOOT_CMD(tmenu_input,8,1, do_tmenu_input,"tmenu_input","tmenu_input item1 [item2...] - wait for touch menu input");
Combining it all
What's this about?
This is a log of my FOSS hobby (and some non-hobby) work on u-boot/Linux
kernel with relation to Allwinner SoC based SBCs and tablets. Mostly H3 based
Orange Pi PC and PC2 and A83T based A711 tablet I got from TBS (Touchless
Biometric Systems AG).