diff --git a/.gitignore b/.gitignore index 06fb2a5..4b54a02 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ *.S *.symvers *.order +kernel/bcm2835_spi_display.mod.c +build +.DS_Store +util/tcCalib +util/tcTest diff --git a/CMakeLists.txt b/CMakeLists.txt index ad6d075..8f9ac4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,4 +256,4 @@ endif() add_executable(fbcp-ili9341 ${sourceFiles}) -target_link_libraries(fbcp-ili9341 pthread bcm_host atomic) +target_link_libraries(fbcp-ili9341 pthread bcm_host atomic rt) diff --git a/README.md b/README.md index 17baa20..b598049 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,33 @@ This repository implements a driver for certain SPI-based LCD displays for Raspb ![PiTFT display](/example.jpg "Adafruit PiTFT 2.8 with ILI9341 controller") The work was motivated by curiosity after seeing this series of videos on the RetroManCave YouTube channel: - - [RetroManCave: Waveshare 3.5" Raspberry Pi Screen | Review](https://www.youtube.com/watch?v=SGMC0t33C50) - - [RetroManCave: Waveshare 3.2" vs 3.5" LCD screen gaming test | Raspberry Pi / RetroPie](https://www.youtube.com/watch?v=8bazEcXemiA) - - [Elecrow 5 Inch LCD Review | RetroPie & Raspberry Pi](https://www.youtube.com/watch?v=8VgNBDMOssg) +- [RetroManCave: Waveshare 3.5" Raspberry Pi Screen | Review](https://www.youtube.com/watch?v=SGMC0t33C50) +- [RetroManCave: Waveshare 3.2" vs 3.5" LCD screen gaming test | Raspberry Pi / RetroPie](https://www.youtube.com/watch?v=8bazEcXemiA) +- [Elecrow 5 Inch LCD Review | RetroPie & Raspberry Pi](https://www.youtube.com/watch?v=8VgNBDMOssg) In these videos, the SPI (GPIO) bus is referred to being the bottleneck. SPI based displays update over a serial data bus, transmitting one bit per clock cycle on the bus. A 320x240x16bpp display hence requires a SPI bus clock rate of 73.728MHz to achieve a full 60fps refresh frequency. Not many SPI LCD controllers can communicate this fast in practice, but are constrained to e.g. a 16-50MHz SPI bus clock speed, capping the maximum update rate significantly. Can we do anything about this? The fbcp-ili9341 project started out as a display driver for the [Adafruit 2.8" 320x240 TFT w/ Touch screen for Raspberry Pi](https://www.adafruit.com/product/1601) display that utilizes the ILI9341 controller. On that display, fbcp-ili9341 can achieve a 60fps update rate, depending on the content that is being displayed. Check out these videos for examples of the driver in action: - - [fbcp-ili9341 frame delivery smoothness test on Pi 3B and Adafruit ILI9341 at 119Hz](https://youtu.be/IqzKT33Rwjc) - - [Latency and tearing test #2: GPIO input to display latency in fbcp-ili9341 and tearing modes](https://www.youtube.com/watch?v=EOICdpjiqv8) - - [Latency and tearing test: KeDei 3.5" 320x480 HDMI vs Adafruit 2.8" PiTFT ILI9341 240x320 SPI](https://www.youtube.com/watch?v=1yvmvv0KtNs) - - [fbcp-ili9341 ported to ILI9486 WaveShare 3.5" (B) SpotPear 320x480 SPI display](https://www.youtube.com/watch?v=dqOLIHOjLq4) - - [Quake 60 fps inside Gameboy Advance (ILI9341)](https://www.youtube.com/watch?v=xmO8t3XlxVM) - - First implementation of a statistics overlay: [fbcp-ili9341 SPI display driver on Adafruit PiTFT 2.8"](http://youtu.be/rKSH048XRjA) - - Initial proof of concept video: [fbcp-ili9341 driver first demo](https://youtu.be/h1jhuR-oZm0) +- [fbcp-ili9341 frame delivery smoothness test on Pi 3B and Adafruit ILI9341 at 119Hz](https://youtu.be/IqzKT33Rwjc) +- [Latency and tearing test #2: GPIO input to display latency in fbcp-ili9341 and tearing modes](https://www.youtube.com/watch?v=EOICdpjiqv8) +- [Latency and tearing test: KeDei 3.5" 320x480 HDMI vs Adafruit 2.8" PiTFT ILI9341 240x320 SPI](https://www.youtube.com/watch?v=1yvmvv0KtNs) +- [fbcp-ili9341 ported to ILI9486 WaveShare 3.5" (B) SpotPear 320x480 SPI display](https://www.youtube.com/watch?v=dqOLIHOjLq4) +- [Quake 60 fps inside Gameboy Advance (ILI9341)](https://www.youtube.com/watch?v=xmO8t3XlxVM) +- First implementation of a statistics overlay: [fbcp-ili9341 SPI display driver on Adafruit PiTFT 2.8"](http://youtu.be/rKSH048XRjA) +- Initial proof of concept video: [fbcp-ili9341 driver first demo](https://youtu.be/h1jhuR-oZm0) ### How It Works Given that the SPI bus can be so constrained on bandwidth, how come fbcp-ili9341 seems to be able to update at up to 60fps? The way this is achieved is by what could be called *adaptive display stream updates*. Instead of uploading each pixel at each display refresh cycle, only the actually changed pixels on screen are submitted to the display. This is doable because the ILI9341 controller, as many other popular controllers, have communication interface functions that allow specifying partial screen updates, down to subrectangles or even individual pixel levels. This allows beating the bandwidth limit: for example in Quake, even though it is a fast pacing game, on average only about 46% of all pixels on screen change each rendered frame. Some parts, such as the UI stay practically constant across multiple frames. Other optimizations are also utilized to squeeze out even more performance: - - The program directly communicates with the BCM2835 ARM Peripherals controller registers, bypassing the usual Linux software stack. - - A hybrid of both Polled Mode SPI and DMA based transfers are utilized. Long sequential transfer bursts are performed using DMA, and when DMA would have too much latency, Polled Mode SPI is applied instead. - - Undocumented BCM2835 features are used to squeeze out maximum bandwidth: [SPI CDIV is driven at even numbers](https://www.raspberrypi.org/forums/viewtopic.php?t=43442) (and not just powers of two), and the [SPI DLEN register is forced in non-DMA mode](https://www.raspberrypi.org/forums/viewtopic.php?t=181154) to avoid an idle 9th clock cycle for each transferred byte. - - Good old **interlacing** is added into the mix: if the amount of pixels that needs updating is detected to be too much that the SPI bus cannot handle it, the driver adaptively resorts to doing an interlaced update, uploading even and odd scanlines at subsequent frames. Once the number of pending pixels to write returns to manageable amounts, progressive updating is resumed. This effectively doubles the maximum display update rate. (If you do not like the visual appearance that interlacing causes, it is easy to disable this by uncommenting the line `#define NO_INTERLACING` in file `config.h`) - - A dedicated SPI communication thread is used in order to keep the SPI bus active at all times. - - A number of other micro-optimization techniques are used, such as batch updating rectangular spans of pixels, merging disjoint-but-close spans of pixels on the same scanline, and latching Column and Page End Addresses to bottom-right corner of the display to be able to cut CASET and PASET messages in mid-communication. +- The program directly communicates with the BCM2835 ARM Peripherals controller registers, bypassing the usual Linux software stack. +- A hybrid of both Polled Mode SPI and DMA based transfers are utilized. Long sequential transfer bursts are performed using DMA, and when DMA would have too much latency, Polled Mode SPI is applied instead. +- Undocumented BCM2835 features are used to squeeze out maximum bandwidth: [SPI CDIV is driven at even numbers](https://www.raspberrypi.org/forums/viewtopic.php?t=43442) (and not just powers of two), and the [SPI DLEN register is forced in non-DMA mode](https://www.raspberrypi.org/forums/viewtopic.php?t=181154) to avoid an idle 9th clock cycle for each transferred byte. +- Good old **interlacing** is added into the mix: if the amount of pixels that needs updating is detected to be too much that the SPI bus cannot handle it, the driver adaptively resorts to doing an interlaced update, uploading even and odd scanlines at subsequent frames. Once the number of pending pixels to write returns to manageable amounts, progressive updating is resumed. This effectively doubles the maximum display update rate. (If you do not like the visual appearance that interlacing causes, it is easy to disable this by uncommenting the line `#define NO_INTERLACING` in file `config.h`) +- A dedicated SPI communication thread is used in order to keep the SPI bus active at all times. +- A number of other micro-optimization techniques are used, such as batch updating rectangular spans of pixels, merging disjoint-but-close spans of pixels on the same scanline, and latching Column and Page End Addresses to bottom-right corner of the display to be able to cut CASET and PASET messages in mid-communication. The result is that the SPI bus can be kept close to 100% saturation, ~94-97% usual, to maximize the utilization rate of the bus, while only transmitting practically the minimum number of bytes needed to describe each new frame. @@ -39,11 +39,11 @@ The result is that the SPI bus can be kept close to 100% saturation, ~94-97% usu The driver has been checked to work (at least some point in the past) on the following systems: - - Raspberry Pi 3 Model B+ with Raspbian Stretch (GCC 6.3.0) - - Raspberry Pi 3 Model B Rev 1.2 with Raspbian Jessie (GCC 4.9.2) and Raspbian Stretch (GCC 6.3.0) - - Raspberry Pi Zero W with Raspbian Jessie (GCC 4.9.2) and Raspbian Stretch (GCC 6.3.0) - - Raspberry Pi 2 Model B - - Raspberry Pi B Rev. 2.0 (old board from Q4 2012, board revision ID 000e) +- Raspberry Pi 3 Model B+ with Raspbian Stretch (GCC 6.3.0) +- Raspberry Pi 3 Model B Rev 1.2 with Raspbian Jessie (GCC 4.9.2) and Raspbian Stretch (GCC 6.3.0) +- Raspberry Pi Zero W with Raspbian Jessie (GCC 4.9.2) and Raspbian Stretch (GCC 6.3.0) +- Raspberry Pi 2 Model B +- Raspberry Pi B Rev. 2.0 (old board from Q4 2012, board revision ID 000e) although not all boards are actively tested on, so ymmv especially on older boards. (Bug fixes welcome, use https://elinux.org/RPi_HardwareHistory to identify which board you are running on) @@ -51,20 +51,20 @@ although not all boards are actively tested on, so ymmv especially on older boar The following LCD displays have been tested: - - [Adafruit 2.8" 320x240 TFT w/ Touch screen for Raspberry Pi](https://www.adafruit.com/product/1601) with ILI9341 controller - - [Adafruit PiTFT 2.2" HAT Mini Kit - 320x240 2.2" TFT - No Touch](https://www.adafruit.com/product/2315) with ILI9340 controller - - [Adafruit PiTFT - Assembled 480x320 3.5" TFT+Touchscreen for Raspberry Pi](https://www.adafruit.com/product/2097) with HX8357D controller - - [Adafruit 128x96 OLED Breakout Board - 16-bit Color 1.27" w/microSD holder](https://www.adafruit.com/product/1673) with SSD1351 controller - - [Waveshare 3.5inch RPi LCD (B) 320*480 Resolution Touch Screen IPS TFT Display](https://www.amazon.co.uk/dp/B01N48NOXI/ref=pe_3187911_185740111_TE_item) with ILI9486 controller - - [maithoga 3.5 inch 8PIN SPI TFT LCD Color Screen with Adapter Board ILI9486](https://www.aliexpress.com/item/3-5-inch-8P-SPI-TFT-LCD-Color-Screen-Module-ILI9486-Drive-IC-320-480-RGB/32828284227.html) with **ILI9486L** controller - - [BuyDisplay.com 320x480 Serial SPI 3.2"TFT LCD Module Display](https://www.buydisplay.com/default/serial-spi-3-2-inch-tft-lcd-module-display-ili9341-power-than-sainsmart) with ILI9341 controller - - [Arduino A000096 1.77" 160x128 LCD Screen](https://store.arduino.cc/arduino-lcd-screen) with ST7735R controller - - [Tontec 3.5" 320x480 LCD Display](https://www.ebay.com/p/Tontec-3-5-Inches-Touch-Screen-for-Raspberry-Pi-Display-TFT-Monitor-480x320-LCD/1649448059) with MZ61581-PI-EXT 2016.1.28 controller - - [Adafruit 1.54" 240x240 Wide Angle TFT LCD Display with MicroSD](https://www.adafruit.com/product/3787) with ST7789 controller - - [WaveShare 240x240, 1.3inch IPS LCD display HAT for Raspberry Pi](https://www.waveshare.com/1.3inch-lcd-hat.htm) with ST7789VW controller - - [WaveShare 128x128, 1.44inch LCD display HAT for Raspberry Pi](https://www.waveshare.com/1.44inch-lcd-hat.htm) with ST7735S controller - - [KeDei 3.5 inch SPI TFTLCD 480*320 16bit/18bit version 6.3 2018/4/9](https://github.com/juj/fbcp-ili9341/issues/40) with MPI3501 controller - - Unbranded 2.8" 320x240 display with ILI9340 controller +- [Adafruit 2.8" 320x240 TFT w/ Touch screen for Raspberry Pi](https://www.adafruit.com/product/1601) with ILI9341 controller +- [Adafruit PiTFT 2.2" HAT Mini Kit - 320x240 2.2" TFT - No Touch](https://www.adafruit.com/product/2315) with ILI9340 controller +- [Adafruit PiTFT - Assembled 480x320 3.5" TFT+Touchscreen for Raspberry Pi](https://www.adafruit.com/product/2097) with HX8357D controller +- [Adafruit 128x96 OLED Breakout Board - 16-bit Color 1.27" w/microSD holder](https://www.adafruit.com/product/1673) with SSD1351 controller +- [Waveshare 3.5inch RPi LCD (B) 320*480 Resolution Touch Screen IPS TFT Display](https://www.amazon.co.uk/dp/B01N48NOXI/ref=pe_3187911_185740111_TE_item) with ILI9486 controller +- [maithoga 3.5 inch 8PIN SPI TFT LCD Color Screen with Adapter Board ILI9486](https://www.aliexpress.com/item/3-5-inch-8P-SPI-TFT-LCD-Color-Screen-Module-ILI9486-Drive-IC-320-480-RGB/32828284227.html) with **ILI9486L** controller +- [BuyDisplay.com 320x480 Serial SPI 3.2"TFT LCD Module Display](https://www.buydisplay.com/default/serial-spi-3-2-inch-tft-lcd-module-display-ili9341-power-than-sainsmart) with ILI9341 controller +- [Arduino A000096 1.77" 160x128 LCD Screen](https://store.arduino.cc/arduino-lcd-screen) with ST7735R controller +- [Tontec 3.5" 320x480 LCD Display](https://www.ebay.com/p/Tontec-3-5-Inches-Touch-Screen-for-Raspberry-Pi-Display-TFT-Monitor-480x320-LCD/1649448059) with MZ61581-PI-EXT 2016.1.28 controller +- [Adafruit 1.54" 240x240 Wide Angle TFT LCD Display with MicroSD](https://www.adafruit.com/product/3787) with ST7789 controller +- [WaveShare 240x240, 1.3inch IPS LCD display HAT for Raspberry Pi](https://www.waveshare.com/1.3inch-lcd-hat.htm) with ST7789VW controller +- [WaveShare 128x128, 1.44inch LCD display HAT for Raspberry Pi](https://www.waveshare.com/1.44inch-lcd-hat.htm) with ST7735S controller +- [KeDei 3.5 inch SPI TFTLCD 480*320 16bit/18bit version 6.3 2018/4/9](https://github.com/juj/fbcp-ili9341/issues/40) with MPI3501 controller +- Unbranded 2.8" 320x240 display with ILI9340 controller ### Installation @@ -76,7 +76,7 @@ This driver does not utilize the [notro/fbtft](https://github.com/notro/fbtft) f This program neither utilizes the default SPI driver, so a line such as `dtparam=spi=on` in `/boot/config.txt` should also be removed so that it will not cause conflicts. -Likewise, if you have any touch controller related dtoverlays active, such as `dtoverlay=ads7846,...` or anything that has a `penirq=` directive, those should be removed as well to avoid conflicts. It would be possible to add touch support to fbcp-ili9341 if someone wants to take a stab at it. +Likewise, if you have any touch controller related dtoverlays active, such as `dtoverlay=ads7846,...` or anything that has a `penirq=` directive, those should be removed as well to avoid conflicts. The driver has its own touch screen driver (thanks to contributors). ##### Building and running @@ -91,7 +91,11 @@ mkdir build cd build cmake [options] .. make -j -sudo ./fbcp-ili9341 +cd ../kernel +./start_kernel_module.sh +cd ../build +tail -f /tmp/TCfifo & +sudo ./fbcp-ili9341 & ``` Note especially the two dots `..` on the CMake line, which denote "up one directory" in this case (instead of referring to "more items go here"). @@ -266,6 +270,10 @@ On the other hand, it is desirable to control how much CPU time fbcp-ili9341 is - In `display.h` there is an option `#define TARGET_FRAME_RATE `. Setting this to a smaller value, such as 30, will trade refresh rate to reduce CPU consumption. +### Configuring touch screen and calibration + +In the 'util' folder, there is a calibration utility and a test utillity that assist with determining the 7 matrix parameters for calibration. Wth the tcCalibration utillity, the program on startup, deletes the config file ('/etc/xpt2046.config' -- it needs permission to do this), sends a USER1 signal to the display driver to signal it to re-load the driver config (it won't find it and will therefore use a 1 to 1 mapping), you touch the three points indicated, the new matrix is written to the config file location, and another signal is sent to the driver to re-load the configuration. Your touch display is now calibrated. The ctTest program is a demo program showing cursor tracking with the pointing device based on the driver-calibrated values. + ### About Input Latency A pleasing aspect of fbcp-ili9341 is that it introduces very little latency overhead: on a 119Hz refreshing ILI9341 display, [fbcp-ili9341 gets pixels as response from GPIO input to screen in well less than 16.66 msecs](https://www.youtube.com/watch?v=EOICdpjiqv8) time. I only have a 120fps recording camera, so can't easily measure delays shorter than that, but rough statistical estimate of slow motion video footage suggests this delay could be as low as 2-3 msecs, dominated by the ~8.4msecs panel refresh rate of the ILI9341. @@ -303,71 +311,71 @@ There are two other main options that affect frame delivery timings, `#define SE This mode uses the DispmanX HDMI vsync signal callback to drive frames to the display. Pros: - - least CPU overhead if content runs at 60Hz - - works on Pi Zero +- least CPU overhead if content runs at 60Hz +- works on Pi Zero Cons: - - animation stutters badly on content that is < 60Hz but also on 60Hz content - - excessive +1 vsync interval input to display latency - - wastes CPU overhead if content runs at less than 60Hz +- animation stutters badly on content that is < 60Hz but also on 60Hz content +- excessive +1 vsync interval input to display latency +- wastes CPU overhead if content runs at less than 60Hz **2. vc_dispmanx_vsync_callback() + self synchronization (top right)**, set `#define USE_GPU_VSYNC` and `#define SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES`: This mode uses the GPU vsync signal, but also aims to find and synchronize to the edge trigger when content is producing frames. This is the default build mode on Pi Zero. Pros: - - works on Pi Zero - - reduced input to display latency compared to previous mode - - content that runs at 60hz stutters less +- works on Pi Zero +- reduced input to display latency compared to previous mode +- content that runs at 60hz stutters less Cons: - - content that runs < 60 Hz still stutters badly - - wastes CPU overhead if content runs at less than 60Hz - - consumes slightly extra CPU compared to previous method +- content that runs < 60 Hz still stutters badly +- wastes CPU overhead if content runs at less than 60Hz +- consumes slightly extra CPU compared to previous method **3. gpu polling thread + sleep heuristic (bottom left)**, unset `#define USE_GPU_VSYNC` and set `#define SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES`: This mode runs a dedicated background thread that drives frames from the GPU to the SPI display. This is the default build mode on Pi 3B. Pros: - - smooth animation at all content frame rates - - low input to display latency +- smooth animation at all content frame rates +- low input to display latency Cons: - - uses excessive CPU time, around +34% more CPU than the vsync signal based approach - - uses excessive GPU time, the VideoCore GPU will be downscaling and snapshotting redundant frames - - when content changes frame rate, has difficulties to adjust quickly - takes a bit of time to ramp to the new frame rate - - requires a continuously running background thread, not feasible on Pi Zero +- uses excessive CPU time, around +34% more CPU than the vsync signal based approach +- uses excessive GPU time, the VideoCore GPU will be downscaling and snapshotting redundant frames +- when content changes frame rate, has difficulties to adjust quickly - takes a bit of time to ramp to the new frame rate +- requires a continuously running background thread, not feasible on Pi Zero **4. gpu polling thread without sleeping (bottom right)**, unset `#define USE_GPU_VSYNC` and unset `#define SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES`: This mode runs the dedicated GPU thread as fast as possible, without attempting to sleep CPU. Pros: - - smoothest animation at all content frame rates - - lowest input to display latency - - adapts instantaneously to variable frame rate content +- smoothest animation at all content frame rates +- lowest input to display latency +- adapts instantaneously to variable frame rate content Cons: - - uses ridiculously much CPU overhead, a full 100% core - - uses ridiculously much GPU overhead, the VideoCore GPU will be very busy downscaling and snapshotting redundant frames - - requires a continuously running background thread, not feasible on Pi Zero +- uses ridiculously much CPU overhead, a full 100% core +- uses ridiculously much GPU overhead, the VideoCore GPU will be very busy downscaling and snapshotting redundant frames +- requires a continuously running background thread, not feasible on Pi Zero ### Known Issues Be aware of the following limitations: ###### No rendered frame delivery via events from VideoCore IV GPU - - The codebase captures screen framebuffers by snapshotting via the VideoCore `vc_dispmanx_snapshot()` API, and the obtained pixels are then routed on to the SPI-based display. This kind of polling is performed, since there does not exist an event-based mechanism to get new frames from the GPU as they are produced. The result is inefficient and can easily cause stuttering, since different applications produce frames at different paces. **Ideally the code would ask the VideoCore API to receive finished frames in callback notifications immediately after they are rendered**, but this kind of functionality does not exist in the current GPU driver stack. In the absence of such event delivery mechanism, the code has to resort to polling snapshots of the display framebuffer using carefully timed heuristics to balance between keeping latency and stuttering low, while not causing excessive power consumption. These heuristics keep continuously guessing the update rate of the animation on screen, and they have been tuned to ensure that CPU usage goes down to 0% when there is no detected activity on screen, but it is certainly not perfect. This GPU limitation is discussed at https://github.com/raspberrypi/userland/issues/440. If you'd like to see fbcp-ili9341 operation reduce latency, stuttering and power consumption, please throw a (kind!) comment or a thumbs up emoji in that bug thread to share that you care about this, and perhaps Raspberry Pi engineers might pick the improvement up on the development roadmap. If this issue is resolved, all of the `#define USE_GPU_VSYNC`, `#define SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES` and `#define SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES` hacks from the previous section could be deleted from the driver, hopefully leading to a best of all worlds scenario without drawbacks. +- The codebase captures screen framebuffers by snapshotting via the VideoCore `vc_dispmanx_snapshot()` API, and the obtained pixels are then routed on to the SPI-based display. This kind of polling is performed, since there does not exist an event-based mechanism to get new frames from the GPU as they are produced. The result is inefficient and can easily cause stuttering, since different applications produce frames at different paces. **Ideally the code would ask the VideoCore API to receive finished frames in callback notifications immediately after they are rendered**, but this kind of functionality does not exist in the current GPU driver stack. In the absence of such event delivery mechanism, the code has to resort to polling snapshots of the display framebuffer using carefully timed heuristics to balance between keeping latency and stuttering low, while not causing excessive power consumption. These heuristics keep continuously guessing the update rate of the animation on screen, and they have been tuned to ensure that CPU usage goes down to 0% when there is no detected activity on screen, but it is certainly not perfect. This GPU limitation is discussed at https://github.com/raspberrypi/userland/issues/440. If you'd like to see fbcp-ili9341 operation reduce latency, stuttering and power consumption, please throw a (kind!) comment or a thumbs up emoji in that bug thread to share that you care about this, and perhaps Raspberry Pi engineers might pick the improvement up on the development roadmap. If this issue is resolved, all of the `#define USE_GPU_VSYNC`, `#define SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES` and `#define SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES` hacks from the previous section could be deleted from the driver, hopefully leading to a best of all worlds scenario without drawbacks. ###### Screen resize freezes DispmanX - - Currently if one resizes the video frame size at runtime, this causes DispmanX API to go sideways. See https://github.com/raspberrypi/userland/issues/461 for more information. Best workaround is to set the desired screen resolution in `/boot/config.txt` and configure all applications to never change that at runtime. +- Currently if one resizes the video frame size at runtime, this causes DispmanX API to go sideways. See https://github.com/raspberrypi/userland/issues/461 for more information. Best workaround is to set the desired screen resolution in `/boot/config.txt` and configure all applications to never change that at runtime. ###### CPU Turbo is needed for good SPI bus bandwidth - - The speed of the SPI bus is linked to the BCM2835 core frequency. This frequency is at 250MHz by default (on e.g. Pi Zero, 3B and 3B+), and under CPU load, the core turbos up to 400MHz. This turboing directly scales up the SPI bus speed by `400/250=+60%` as well. Therefore when choosing the SPI `CDIV` value to use, one has to pick one that works for both idle and turbo clock speeds. Conversely, the BCM core reverts to non-turbo speed when there is only light CPU load active, and this slows down the display, so if an application is graphically intensive but light on CPU, the SPI display bus does not get a chance to run at maximum speeds. A way to work around this is to force the BCM core to always stay in its turbo state with `force_turbo=1` option in `/boot/config.txt`, but this has an unfortunate effect of causing the ARM CPU to always run in turbo speed as well, consuming excessive amounts of power. At the time of writing, there does not yet exist a good solution to have both power saving and good performance. This limitation is being discussed in more detail at https://github.com/raspberrypi/firmware/issues/992. +- The speed of the SPI bus is linked to the BCM2835 core frequency. This frequency is at 250MHz by default (on e.g. Pi Zero, 3B and 3B+), and under CPU load, the core turbos up to 400MHz. This turboing directly scales up the SPI bus speed by `400/250=+60%` as well. Therefore when choosing the SPI `CDIV` value to use, one has to pick one that works for both idle and turbo clock speeds. Conversely, the BCM core reverts to non-turbo speed when there is only light CPU load active, and this slows down the display, so if an application is graphically intensive but light on CPU, the SPI display bus does not get a chance to run at maximum speeds. A way to work around this is to force the BCM core to always stay in its turbo state with `force_turbo=1` option in `/boot/config.txt`, but this has an unfortunate effect of causing the ARM CPU to always run in turbo speed as well, consuming excessive amounts of power. At the time of writing, there does not yet exist a good solution to have both power saving and good performance. This limitation is being discussed in more detail at https://github.com/raspberrypi/firmware/issues/992. ###### Raspbian + 32-bit only(?) - - At the moment fbcp-ili9341 is only likely to work on 32-bit OSes, on Raspbian/Ubuntu/Debian family of distributions, where Broadcom and DispmanX libraries are available. 64-bit operating systems do not currently work (see [issue #43](https://github.com/juj/fbcp-ili9341/issues/43)). It should be possible to port the driver to 64-bit and other OSes, though the amount of work has not been explored. +- At the moment fbcp-ili9341 is only likely to work on 32-bit OSes, on Raspbian/Ubuntu/Debian family of distributions, where Broadcom and DispmanX libraries are available. 64-bit operating systems do not currently work (see [issue #43](https://github.com/juj/fbcp-ili9341/issues/43)). It should be possible to port the driver to 64-bit and other OSes, though the amount of work has not been explored. For more known issues and limitations, check out the [bug tracker](https://github.com/juj/fbcp-ili9341/issues), especially the entries marked *retired*, for items that are beyond current scope. @@ -428,10 +436,10 @@ If fbcp-ili9341 does not support your display controller, you will have to write Perhaps. This is a more recent experimental feature that may not be as stable, and there are some limitations, but 3-wire ("9-bit") SPI display support is now available. If you have a 3-wire SPI display, i.e. one that does not have a Data/Control (DC) GPIO pin to connect, configure it via CMake with directive `-DGPIO_TFT_DATA_CONTROL=-1` to tell fbcp-ili9341 that it should be driving the display with 3-wire protocol. Current limitations of 3-wire communication are: - - The performance option `ALL_TASKS_SHOULD_DMA` is currently not supported, there is an issue with DMA chaining that prevents this from being enabled. As result, CPU usage on 3-wire displays will be slightly higher than on 4-wire displays. - - The performance option `OFFLOAD_PIXEL_COPY_TO_DMA_CPP` is currently not supported. As a result, 3-wire displays may not work that well on single core Pis like Pi Zero. - - This has only been tested on my Adafruit SSD1351 128x96 RGB OLED display, which can be soldered to operate in 3-wire SPI mode, so testing has not been particularly extensive. - - Displays that have a 16-bit wide command word, such as ILI9486, do not currently work in 3-wire ("17-bit") mode. (But ILI9486L has 8-bit command word, so that does work) +- The performance option `ALL_TASKS_SHOULD_DMA` is currently not supported, there is an issue with DMA chaining that prevents this from being enabled. As result, CPU usage on 3-wire displays will be slightly higher than on 4-wire displays. +- The performance option `OFFLOAD_PIXEL_COPY_TO_DMA_CPP` is currently not supported. As a result, 3-wire displays may not work that well on single core Pis like Pi Zero. +- This has only been tested on my Adafruit SSD1351 128x96 RGB OLED display, which can be soldered to operate in 3-wire SPI mode, so testing has not been particularly extensive. +- Displays that have a 16-bit wide command word, such as ILI9486, do not currently work in 3-wire ("17-bit") mode. (But ILI9486L has 8-bit command word, so that does work) #### Does fbcp-ili9341 work with I2C, DPI, MIPI DSI or USB connected displays? @@ -554,7 +562,7 @@ The ILI9486L controller based maithoga display runs a bit faster than ILI9486 Wa If manufacturing variances turn out not to be high between copies, and you'd like to have a bigger 320x480 display instead of a 240x320 one, then it is recommended to avoid ILI9486, they indeed are slow. -The KeDei v6.3 display with MPI3501 controller takes the crown of being horrible, in all aspects imaginable. It is able to run at 33.33 MHz, but due to technical design limitations of the display (see [#40](https://github.com/juj/fbcp-ili9341/issues/40#issuecomment-441480557)), effective bus speed is halved, and only about 72% utilization of the remaining bus rate is achieved. DMA cannot be used, so CPU usage will be off the charts. Even though fbcp-ili9341 supports this display, level of support is expected to be poor, because the hardware design is a closed secret without open documentation publicly available from the manufacturer. Stay clear of KeDei or MPI3501 displays. +The KeDei v6.3 display with MPI3501 controller takes the crown of being horrible, in all aspects imaginable. It is able to run at 33.33 MHz, but due to technical design limitations of the display (see [#40](https://github.com/juj/fbcp-ili9341/issues/40#issuecomment-441480557)), effective bus speed is halved, and only about 72% utilization of the remaining bus rate is achieved. DMA cannot be used, so CPU usage will be off the charts. Even though fbcp-ili9341 supports this display. The hardware design is closed BUT it is not that special and is so close to other data sheets of like chips, 98% of the commands work as described on other data sheets. What does NOT work is backlight control by software, the commands for that don't cause errors but they also have no effect. Stay clear of KeDei or MPI3501 displays if you need any sort of motion. They are fine for control displays that are nearly static and have only partial screen refreshes. The Tontec MZ61581 controller based 320x480 3.5" display on the other hand can be driven insanely fast at up to 140MHz! These seem to be quite hard to come by though and they are expensive. Tontec seems to have gone out of business and for example the domain itontec.com from which the supplied instructions sheet asks to download original drivers from is no longer registered. I was able to find one from eBay for testing. @@ -567,46 +575,45 @@ Ultimately, it should be noted that parallel displays (DPI) are the proper metho ### Resources The following links proved helpful when writing this: - - [ARM BCM2835 Peripherals Manual PDF](https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf), - - [ILI9341 Display Controller Manual PDF](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf), - - [notro/fbtft](https://github.com/notro/fbtft): Linux Framebuffer drivers for small TFT LCD display modules, - - [BCM2835 driver](http://www.airspayce.com/mikem/bcm2835/) for Raspberry Pi, - - [tasanakorn/rpi-fbcp](https://github.com/tasanakorn/rpi-fbcp), original framebuffer driver, - - [tasanakorn/rpi-fbcp/#16](https://github.com/tasanakorn/rpi-fbcp/issues/16), discussion about performance, - - [Tomáš Suk, Cyril Höschl IV, and Jan Flusser, Rectangular Decomposition of Binary Images.](http://library.utia.cas.cz/separaty/2012/ZOI/suk-rectangular%20decomposition%20of%20binary%20images.pdf), a useful research paper about merging monochrome bitmap images to rectangles, which gave good ideas for optimizing SPI span merges across multiple scan lines, - - [VC DispmanX source code](https://github.com/raspberrypi/userland/blob/master/interface/vmcs_host/vc_vchi_dispmanx.c) (more or less the only official documentation bit on DispmanX I could ever find) +- [ARM BCM2835 Peripherals Manual PDF](https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf), +- [ILI9341 Display Controller Manual PDF](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf), +- [notro/fbtft](https://github.com/notro/fbtft): Linux Framebuffer drivers for small TFT LCD display modules, +- [BCM2835 driver](http://www.airspayce.com/mikem/bcm2835/) for Raspberry Pi, +- [tasanakorn/rpi-fbcp](https://github.com/tasanakorn/rpi-fbcp), original framebuffer driver, +- [tasanakorn/rpi-fbcp/#16](https://github.com/tasanakorn/rpi-fbcp/issues/16), discussion about performance, +- [Tomáš Suk, Cyril Höschl IV, and Jan Flusser, Rectangular Decomposition of Binary Images.](http://library.utia.cas.cz/separaty/2012/ZOI/suk-rectangular%20decomposition%20of%20binary%20images.pdf), a useful research paper about merging monochrome bitmap images to rectangles, which gave good ideas for optimizing SPI span merges across multiple scan lines, +- [VC DispmanX source code](https://github.com/raspberrypi/userland/blob/master/interface/vmcs_host/vc_vchi_dispmanx.c) (more or less the only official documentation bit on DispmanX I could ever find) ### I Want To Contribute / Future Work / TODOs If you would like to help push Raspberry Pi SPI display support further, there are always more things to do in the project. Here is a list of ideas and TODOs for recognized work items to contribute, roughly rated in order of increasing difficulty. - - Vote up issue [raspberrypi/userland/#440](https://github.com/raspberrypi/userland/issues/440) if you would like to see Raspberry Pi Foundation improve CPU performance and reduce latency of the Pi when used with SPI displays. - - Vote up issue [raspberrypi/userland/#461](https://github.com/raspberrypi/userland/issues/461) if you would like to see fbcp-ili9341 not die (due to DispmanX dying) when HDMI display resolution changes. - - Vote up issue [raspberrypi/firmware/#992](https://github.com/raspberrypi/firmware/issues/992) if you would like to see Raspberry Pi SPI bus to have high throughput even when the Pi CPU is not under heavy CPU load (better SPI throughput with lower power consumption), a performance feature only SDHOST on the Pi currently enjoys. - - Benchmark fbcp-ili9341 performance in your use case with CPU tool `top`/`htop`, or with a power meter off the wall and report the results. - - Do you have a display with an unlisted or unknown display controller? Post close up photos of it to an issue in the tracker, and report if you were able to make it work with fbcp-ili9341? - - Did you have to do something unexpected or undocumented to get fbcp-ili9341 to work in your environment or use case? Write up a tutorial or record a video to let people know about the gotchas. - - If you have access to a high frequency scope/logic analyzer (~128MHz), audit the utilization of the SPI MOSI bus to find any remaining idle times on the bus, and analyze their sources. - - Port fbcp-ili9341 to work as a static code library that one can link to another application for CPU-based drawing directly to display, bypassing inefficiencies and latency of the general purpose Linux DispmanX/graphics stack. - - Improve existing display initialization routines with options to control e.g. gamma curves, color saturation, driving voltages, refresh rates or other potentially useful features that the display controller protocols expose. - - Add support to fbcp-ili9341 to a new display controller. (e.g. [#26](https://github.com/juj/fbcp-ili9341/issues/26)) - - Implement support for reading the MISO line for display identification numbers/strings for potentially interesting statistics (could some of the displays be autodetected this way?) - - Add support for other color modes, like RGB666 or RGB888. Currently fbcp-ili9341 only knows about RGB565 display mode. - - Implement a kernel module that enables userland programs to allocate DMA channels, which fbcp-ili9341 could use to amicably reserve its own DMA channels without danger of conflicting. - - Implement support for touch control while fbcp-ili9341 is active. ([#33](https://github.com/juj/fbcp-ili9341/issues/33)) - - Implement support for SPI-based SD card readers that are sometimes attached to displays. - - Port fbcp-ili9341 to work with I2C displays. - - Port more key algorithms to ARM assembly to optimize performance of fbcp-ili9341 in hotspots, or optimize execution in some other ways? - - Add support to building fbcp-ili9341 on another operating system than Raspbian. (see [#43](https://github.com/juj/fbcp-ili9341/issues/43)) - - Add support for building on 64-bit operating systems. (see [#43](https://github.com/juj/fbcp-ili9341/issues/43)) - - Port fbcp-ili9341 over to a new single-board computer hardware. (e.g. [#30](https://github.com/juj/fbcp-ili9341/issues/30)) - - Improve support for 3-wire displays, e.g. for 1) "17-bit" 3-wire communication, 2) fix up `SPI_3WIRE_PROTOCOL` + `ALL_TASKS_SHOULD_DMA` to work together, or 3) fix up `SPI_3WIRE_PROTOCOL` + `OFFLOAD_PIXEL_COPY_TO_DMA_CPP` to work together. - - Optimize away unnecessary zero padding that 3-wire communication currently incurs, by keeping a queue of leftover untransmitted partial bits of a byte, and piggybacking them onto the next transfer that comes in. - - Port the high performance DMA-based SPI communication technique from fbcp-ili9341 over to another project that uses the SPI bus for something else, for close to 100% saturation of the SPI bus in the project. - - Improve the implementation of chaining DMA transfers to not only chain transfers within a single SPI task, but also across multiple SPI tasks. - - Optimize `ALL_TASKS_SHOULD_DMA` mode to be always superior in performance and CPU usage so that the non-`ALL_TASKS_SHOULD_DMA` path can be dropped from the codebase. (probably requires the above chaining to function efficiently) - - If you are knowledgeable with BCM2835 DMA, investigate whether the hacky dance where two DMA channels need to be used to reset and resume DMA SPI transfers when chaining, can be avoided? - - If you have contacts with Broadcom, ask them to promote use of the SoC hardware with DMA chaining + mixed SPI & non-SPI tasks as a first class tested use case. Current DMA SPI hardware behavior of BCM2835 is, to say the least, surprising. +- Vote up issue [raspberrypi/userland/#440](https://github.com/raspberrypi/userland/issues/440) if you would like to see Raspberry Pi Foundation improve CPU performance and reduce latency of the Pi when used with SPI displays. +- Vote up issue [raspberrypi/userland/#461](https://github.com/raspberrypi/userland/issues/461) if you would like to see fbcp-ili9341 not die (due to DispmanX dying) when HDMI display resolution changes. +- Vote up issue [raspberrypi/firmware/#992](https://github.com/raspberrypi/firmware/issues/992) if you would like to see Raspberry Pi SPI bus to have high throughput even when the Pi CPU is not under heavy CPU load (better SPI throughput with lower power consumption), a performance feature only SDHOST on the Pi currently enjoys. +- Benchmark fbcp-ili9341 performance in your use case with CPU tool `top`/`htop`, or with a power meter off the wall and report the results. +- Do you have a display with an unlisted or unknown display controller? Post close up photos of it to an issue in the tracker, and report if you were able to make it work with fbcp-ili9341? +- Did you have to do something unexpected or undocumented to get fbcp-ili9341 to work in your environment or use case? Write up a tutorial or record a video to let people know about the gotchas. +- If you have access to a high frequency scope/logic analyzer (~128MHz), audit the utilization of the SPI MOSI bus to find any remaining idle times on the bus, and analyze their sources. +- Port fbcp-ili9341 to work as a static code library that one can link to another application for CPU-based drawing directly to display, bypassing inefficiencies and latency of the general purpose Linux DispmanX/graphics stack. +- Improve existing display initialization routines with options to control e.g. gamma curves, color saturation, driving voltages, refresh rates or other potentially useful features that the display controller protocols expose. +- Add support to fbcp-ili9341 to a new display controller. (e.g. [#26](https://github.com/juj/fbcp-ili9341/issues/26)) +- Implement support for reading the MISO line for display identification numbers/strings for potentially interesting statistics (could some of the displays be autodetected this way?) +- Add support for other color modes, like RGB666 or RGB888. Currently fbcp-ili9341 only knows about RGB565 display mode. +- Implement a kernel module that enables userland programs to allocate DMA channels, which fbcp-ili9341 could use to amicably reserve its own DMA channels without danger of conflicting. +- üImplement support for SPI-based SD card readers that are sometimes attached to displays. +- Port fbcp-ili9341 to work with I2C displays. +- Port more key algorithms to ARM assembly to optimize performance of fbcp-ili9341 in hotspots, or optimize execution in some other ways? +- Add support to building fbcp-ili9341 on another operating system than Raspbian. (see [#43](https://github.com/juj/fbcp-ili9341/issues/43)) +- Add support for building on 64-bit operating systems. (see [#43](https://github.com/juj/fbcp-ili9341/issues/43)) +- Port fbcp-ili9341 over to a new single-board computer hardware. (e.g. [#30](https://github.com/juj/fbcp-ili9341/issues/30)) +- Improve support for 3-wire displays, e.g. for 1) "17-bit" 3-wire communication, 2) fix up `SPI_3WIRE_PROTOCOL` + `ALL_TASKS_SHOULD_DMA` to work together, or 3) fix up `SPI_3WIRE_PROTOCOL` + `OFFLOAD_PIXEL_COPY_TO_DMA_CPP` to work together. +- Optimize away unnecessary zero padding that 3-wire communication currently incurs, by keeping a queue of leftover untransmitted partial bits of a byte, and piggybacking them onto the next transfer that comes in. +- Port the high performance DMA-based SPI communication technique from fbcp-ili9341 over to another project that uses the SPI bus for something else, for close to 100% saturation of the SPI bus in the project. +- Improve the implementation of chaining DMA transfers to not only chain transfers within a single SPI task, but also across multiple SPI tasks. +- Optimize `ALL_TASKS_SHOULD_DMA` mode to be always superior in performance and CPU usage so that the non-`ALL_TASKS_SHOULD_DMA` path can be dropped from the codebase. (probably requires the above chaining to function efficiently) +- If you are knowledgeable with BCM2835 DMA, investigate whether the hacky dance where two DMA channels need to be used to reset and resume DMA SPI transfers when chaining, can be avoided? +- If you have contacts with Broadcom, ask them to promote use of the SoC hardware with DMA chaining + mixed SPI & non-SPI tasks as a first class tested use case. Current DMA SPI hardware behavior of BCM2835 is, to say the least, surprising. ### License @@ -615,3 +622,4 @@ This driver is licensed under the MIT License. See LICENSE.txt. In nonlegal term If you found fbcp-ili9341 useful, it makes me happy to hear back about the projects it found a home in. If you did a build or a project where fbcp-ili9341 worked out, it'd be great to see a video or some photos or read about your experiences. I hope you build something you enjoy! + diff --git a/XPT2046.cpp b/XPT2046.cpp new file mode 100644 index 0000000..07c8c57 --- /dev/null +++ b/XPT2046.cpp @@ -0,0 +1,290 @@ +/** + * @file XPT2046.cpp + * @date 19.02.2016 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is originally part of the XPT2046 driver for Arduino. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without restriction, + * including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "XPT2046.h" + +#include +#include + +#define XPT2046_CFG_START 1<<7 + +#define XPT2046_CFG_MUX(v) ((v&0b111) << (4)) + +#define XPT2046_CFG_8BIT 1<<3 +#define XPT2046_CFG_12BIT (0) + +#define XPT2046_CFG_SER 1<<2 +#define XPT2046_CFG_DFR (0) + +#define XPT2046_CFG_PWR(v) ((v&0b11)) + +#define XPT2046_MUX_Y 0b101 +#define XPT2046_MUX_X 0b001 + +#define XPT2046_MUX_Z1 0b011 +#define XPT2046_MUX_Z2 0b100 + +#define INTERRUPTDEC 10 +#define MAXLINE 128 + +XPT2046::XPT2046() { + spi_cs = 0; + z_average = 0; + tcfifo = "/tmp/TCfifo"; + calibFile = "/etc/xpt2046.conf"; + + _maxValue = 0x0fff; + _width = 480; + _height = 320; + _rotation = 0; + + _minX = 0; + _minY = 0; + + _maxX = _maxValue; + _maxY = _maxValue; + + _lastX = -1; + _lastY = -1; + + _minChange = 10; + + interruptpoll = INTERRUPTDEC; + + spi_cs = + 1 << 0 | //Chip select 1 + 0 << 3 | //low idle clock polarity + 0 << 6 | //chip select active low + 0 << 22 | //chip select low polarity + 0 << 8 | //DMA disabled + 0 << 11; //Manual chip select + + // FIFO file path + // Creating the named file(FIFO) + // mkfifo(, ) + mkfifo(tcfifo, 0666); + initCalibration(); +} + + +XPT2046::~XPT2046() { +} + +int XPT2046::SpiWriteAndRead(unsigned char *data, int length) +{ + for (int i = 0; i < length; i++) + { + spi->cs = spi_cs | 1 << 7;//Set TA to high + + while (!(spi->cs & (1 << 18))) ; //Poll TX Fifo until it has space + + spi->fifo = data[i]; + + while(! (spi->cs & (1 << 17))) ; //Wait until RX FIFO contains data + + uint32_t spi_fifo = spi->fifo; + + spi->cs = (spi_cs & (~(1 << 7))) | 1 << 5 | 1 << 4; //Set TA to LOW //Clear Fifo + + data[i] = spi_fifo; + } + + return 0; +} + +void XPT2046::read_touchscreen(bool interruptEnable) { + uint16_t x, y, z; + + uint32_t old_spi_cs = spi->cs; + uint32_t old_spi_clk = spi->clk; + //printBits(sizeof(old_spi_cs), (void*)&(old_spi_cs)); + spi->clk = 256; + + // touch on low + read(&x, &y, &z); + if (abs(x - _lastX) > _minChange || abs(y - _lastY) > _minChange) { + _lastX = x; + _lastY = y; + } + + if(interruptEnable) { + // SPI requires 32bit alignment + uint8_t buf[3] = { + // re-enable interrupt + (XPT2046_CFG_START | XPT2046_CFG_12BIT | XPT2046_CFG_DFR | XPT2046_CFG_MUX(XPT2046_MUX_Z2)| XPT2046_CFG_PWR(0)), 0x00, 0x00 + }; + SpiWriteAndRead(buf, 3); + } + + if (z > 100) + { + uint16_t words16Write[4] = { x, y, z, 0x0000FFFF}; + fd = open(tcfifo, O_WRONLY | O_NONBLOCK); + + write(fd, words16Write, 4 * sizeof(uint16_t)); + close(fd); + this->lastTouchTick = tick(); + } + + //spi->cs = (old_spi_cs | 1 << 4 | 1 << 5) & (~(1 << 7)); //Clear Fifos and TA + spi->cs = old_spi_cs; + spi->clk = old_spi_clk; +} + +void XPT2046::setRotation(uint8_t m) { + _rotation = m % 4; +} + +void XPT2046::initCalibration() { + FILE *stream; + char *line = NULL; + int reti; + size_t len = 0; + ssize_t nread; + + stream = fopen(calibFile, "r"); + if(stream != NULL) + { + while ((nread = getline(&line, &len, stream)) != -1) { + //fprintf(stdout,"scanline: '%s'", line); + reti = sscanf((const char*)line,"%d,%d,%d,%d,%d,%d,%d", + &calib.An, &calib.Bn, &calib.Cn, &calib.Dn, &calib.En, &calib.Fn,&calib.Divider); + } + free(line); + fclose(stream); + } else { // Make standard identity calibration -- no translation + // Target on-screen points + POINT pointTargets[3] = { + {15, 15}, + {-15, 15}, + {15, -15}, + }; + // Set curser values initially + // Update for screen dimensions + for(int i = 0; i<3;i++ ) { + pointTargets[i].x = (pointTargets[i].x < 0 ? _width + pointTargets[i].x : pointTargets[i].x); + pointTargets[i].y = (pointTargets[i].y < 0 ? _height + pointTargets[i].y : pointTargets[i].y); + fprintf(stdout, "%d,%d\n", pointTargets[i].x, pointTargets[i].y); + } + setCalibrationMatrix( (POINT*)pointTargets, (POINT*)pointTargets, &calib); // initialized 1:1 matrix + } + fprintf(stdout,"M: %d,%d,%d,%d,%d,%d,%d\r\n" + ,calib.An,calib.Bn,calib.Cn,calib.Dn,calib.En,calib.Fn,calib.Divider ); +} + + +void XPT2046::read(uint16_t * oX, uint16_t * oY, uint16_t * oZ) { + uint16_t x, y; + POINT pointCorrected, pointRaw; + + readRaw(&x, &y, oZ); + + pointRaw.x = x; + pointRaw.y = y; + + if(pointRaw.x < _minX) { + pointRaw.x = 0; + } else if(pointRaw.x > _maxX) { + pointRaw.x = _width; + } else { + pointRaw.x -= _minX; + pointRaw.x = ((pointRaw.x << 8) / (((_maxX - _minX) << 8) / (_width << 8)) )>> 8; + } + + if(pointRaw.y < _minY) { + pointRaw.y = 0; + } else if(pointRaw.y > _maxY) { + pointRaw.y = _height; + } else { + pointRaw.y -= _minY; + pointRaw.y = ((pointRaw.y << 8) / (((_maxY - _minY) << 8) / (_height << 8))) >> 8; + } + + getDisplayPoint((POINT *)&pointCorrected,(POINT *)&pointRaw,&calib); + + *oX = pointCorrected.x; + *oY = pointCorrected.y; + *oZ = std::max(0,4096 - (int)(*oZ)); +} + +void XPT2046::readRaw(uint16_t * oX, uint16_t * oY, uint16_t * oZ) { + uint32_t x = 0; + uint32_t y = 0; + uint32_t z1 = 0; + uint32_t z2 = 0; + uint8_t i = 0; + + for(; i < 4; i++) { // Sampling + // SPI requires 32bit alignment + uint8_t buf[12] = { + (XPT2046_CFG_START | XPT2046_CFG_12BIT | XPT2046_CFG_DFR | XPT2046_CFG_MUX(XPT2046_MUX_Y) | XPT2046_CFG_PWR(3)), 0x00, 0x00, + (XPT2046_CFG_START | XPT2046_CFG_12BIT | XPT2046_CFG_DFR | XPT2046_CFG_MUX(XPT2046_MUX_X) | XPT2046_CFG_PWR(3)), 0x00, 0x00, + (XPT2046_CFG_START | XPT2046_CFG_12BIT | XPT2046_CFG_DFR | XPT2046_CFG_MUX(XPT2046_MUX_Z1)| XPT2046_CFG_PWR(3)), 0x00, 0x00, + (XPT2046_CFG_START | XPT2046_CFG_12BIT | XPT2046_CFG_DFR | XPT2046_CFG_MUX(XPT2046_MUX_Z2)| XPT2046_CFG_PWR(3)), 0x00, 0x00 + }; + + SpiWriteAndRead(buf, 12); + + y += (buf[1] << 8 | buf[2])>>3; + x += (buf[4] << 8 | buf[5])>>3; + z1 += (buf[7] << 8 | buf[8])>>3; + z2 += (buf[10] << 8 | buf[11])>>3; + } + + if(i == 0) { + *oX = 0; + *oY = 0; + *oZ = 0; + return; + } + + x /= i; + y /= i; + z1 /= i; + z2 /= i; + + switch(_rotation) { + case 0: + default: + break; + case 1: + x = (_maxValue - x); + y = (_maxValue - y); + break; + case 2: + y = (_maxValue - y); + break; + case 3: + x = (_maxValue - x); + break; + } + + int z = z1 + _maxValue - z2; + + *oX = x; + *oY = y; + *oZ = z2; +} diff --git a/XPT2046.h b/XPT2046.h new file mode 100644 index 0000000..af517a2 --- /dev/null +++ b/XPT2046.h @@ -0,0 +1,121 @@ +/** + * @file XPT2046.h + * @date 19.02.2016 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the XPT2046 driver for Arduino. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the “Software”), to deal in the Software without restriction, + * including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef XPT2046_H_ +#define XPT2046_H_ + +#include +#include //Needed for SPI port +#include //Needed for SPI port +#include //Needed for SPI port +#include +#include +#include +#include +#include +#include +#include + +#include "spi_user.h" +#include "calibrate.h" + + +class XPT2046 { + public: + + XPT2046(); + + ~XPT2046(); + + int SpiWriteAndRead(unsigned char *data, int length); + + void read_touchscreen(bool interruptEnable); + + void setRotation(uint8_t m); + void initCalibration(); + + void read(uint16_t * oX, uint16_t * oY, uint16_t * oZ); + void readRaw(uint16_t * oX, uint16_t * oY, uint16_t * oZ); + + uint64_t ticksSinceLastTouch() { + uint64_t now = tick(); + return now - this->lastTouchTick; + } + + static void printBits(size_t const size, void const * const ptr) + { + unsigned char *b = (unsigned char*) ptr; + unsigned char byte; + int i, j; + + for (i = size - 1; i >= 0; i--) + { + for (j = 7; j >= 0; j--) + { + byte = (b[i] >> j) & 1; + printf("%u", byte); + } + printf(" "); + } + puts(""); + } + + protected: + + uint16_t _width; + uint16_t _height; + + uint16_t _rotation; + + uint16_t _minX; + uint16_t _minY; + + uint16_t _maxX; + uint16_t _maxY; + + uint16_t _maxValue; + + int _lastX; + int _lastY; + int fd; + uint64_t lastTouchTick; + + uint16_t _minChange; + + uint32_t spi_cs ; + + uint32_t z_average ; + + MATRIX calib; + + const char * tcfifo ; + const char *calibFile ; + int interruptpoll ; +}; + + + +#endif /* XPT2046_H_ */ diff --git a/calibrate.cpp b/calibrate.cpp new file mode 100755 index 0000000..590e4d5 --- /dev/null +++ b/calibrate.cpp @@ -0,0 +1,364 @@ +/* + * + * Copyright (c) 2001, Carlos E. Vidales. All rights reserved. + * + * This sample program was written and put in the public domain + * by Carlos E. Vidales. The program is provided "as is" + * without warranty of any kind, either expressed or implied. + * If you choose to use the program within your own products + * you do so at your own risk, and assume the responsibility + * for servicing, repairing or correcting the program should + * it prove defective in any manner. + * You may copy and distribute the program's source code in any + * medium, provided that you also include in each copy an + * appropriate copyright notice and disclaimer of warranty. + * You may also modify this program and distribute copies of + * it provided that you include prominent notices stating + * that you changed the file(s) and the date of any change, + * and that you do not charge any royalties or licenses for + * its use. + * + * + * + * File Name: calibrate.cpp + * + * + * This file contains functions that implement calculations + * necessary to obtain calibration factors for a touch screen + * that suffers from multiple distortion effects: namely, + * translation, scaling and rotation. + * + * The following set of equations represent a valid display + * point given a corresponding set of touch screen points: + * + * + * /- -\ + * /- -\ /- -\ | | + * | | | | | Xs | + * | Xd | | A B C | | | + * | | = | | * | Ys | + * | Yd | | D E F | | | + * | | | | | 1 | + * \- -/ \- -/ | | + * \- -/ + * + * + * where: + * + * (Xd,Yd) represents the desired display point + * coordinates, + * + * (Xs,Ys) represents the available touch screen + * coordinates, and the matrix + * + * /- -\ + * |A,B,C| + * |D,E,F| represents the factors used to translate + * \- -/ the available touch screen point values + * into the corresponding display + * coordinates. + * + * + * Note that for practical considerations, the utilitities + * within this file do not use the matrix coefficients as + * defined above, but instead use the following + * equivalents, since floating point math is not used: + * + * A = An/Divider + * B = Bn/Divider + * C = Cn/Divider + * D = Dn/Divider + * E = En/Divider + * F = Fn/Divider + * + * + * + * The functions provided within this file are: + * + * setCalibrationMatrix() - calculates the set of factors + * in the above equation, given + * three sets of test points. + * getDisplayPoint() - returns the actual display + * coordinates, given a set of + * touch screen coordinates. + * translateRawScreenCoordinates() - helper function to transform + * raw screen points into values + * scaled to the desired display + * resolution. + * + * + * Revisions: Kevin Peck 2019 + */ + + +#define _CALIBRATE_C_ + + + +/****************************************************/ +/* */ +/* Included files */ +/* */ +/****************************************************/ + +#include "calibrate.h" + + +/****************************************************/ +/* */ +/* Local Definitions and macros */ +/* */ +/****************************************************/ + + + +/****************************************************/ +/* */ +/* Global variables */ +/* */ +/****************************************************/ + + + +/****************************************************/ +/* */ +/* Forward Declaration of local functions */ +/* */ +/****************************************************/ + + + + + + +/********************************************************************** + * + * Function: setCalibrationMatrix() + * + * Description: Calling this function with valid input data + * in the display and screen input arguments + * causes the calibration factors between the + * screen and display points to be calculated, + * and the output argument - matrixPtr - to be + * populated. + * + * This function needs to be called only when new + * calibration factors are desired. + * + * + * Argument(s): displayPtr (input) - Pointer to an array of three + * sample, reference points. + * screenPtr (input) - Pointer to the array of touch + * screen points corresponding + * to the reference display points. + * matrixPtr (output) - Pointer to the calibration + * matrix computed for the set + * of points being provided. + * + * + * From the article text, recall that the matrix coefficients are + * resolved to be the following: + * + * + * Divider = (Xs0 - Xs2)*(Ys1 - Ys2) - (Xs1 - Xs2)*(Ys0 - Ys2) + * + * + * + * (Xd0 - Xd2)*(Ys1 - Ys2) - (Xd1 - Xd2)*(Ys0 - Ys2) + * A = --------------------------------------------------- + * Divider + * + * + * (Xs0 - Xs2)*(Xd1 - Xd2) - (Xd0 - Xd2)*(Xs1 - Xs2) + * B = --------------------------------------------------- + * Divider + * + * + * Ys0*(Xs2*Xd1 - Xs1*Xd2) + + * Ys1*(Xs0*Xd2 - Xs2*Xd0) + + * Ys2*(Xs1*Xd0 - Xs0*Xd1) + * C = --------------------------------------------------- + * Divider + * + * + * (Yd0 - Yd2)*(Ys1 - Ys2) - (Yd1 - Yd2)*(Ys0 - Ys2) + * D = --------------------------------------------------- + * Divider + * + * + * (Xs0 - Xs2)*(Yd1 - Yd2) - (Yd0 - Yd2)*(Xs1 - Xs2) + * E = --------------------------------------------------- + * Divider + * + * + * Ys0*(Xs2*Yd1 - Xs1*Yd2) + + * Ys1*(Xs0*Yd2 - Xs2*Yd0) + + * Ys2*(Xs1*Yd0 - Xs0*Yd1) + * F = --------------------------------------------------- + * Divider + * + * + * Return: OK - the calibration matrix was correctly + * calculated and its value is in the + * output argument. + * NOT_OK - an error was detected and the + * function failed to return a valid + * set of matrix values. + * The only time this sample code returns + * NOT_OK is when Divider == 0 + * + * + * + * NOTE! NOTE! NOTE! + * + * setCalibrationMatrix() and getDisplayPoint() will do fine + * for you as they are, provided that your digitizer + * resolution does not exceed 10 bits (1024 values). Higher + * resolutions may cause the integer operations to overflow + * and return incorrect values. If you wish to use these + * functions with digitizer resolutions of 12 bits (4096 + * values) you will either have to a) use 64-bit signed + * integer variables and math, or b) judiciously modify the + * operations to scale results by a factor of 2 or even 4. + * + * + */ +int setCalibrationMatrix( POINT * displayPtr, + POINT * screenPtr, + MATRIX * matrixPtr) +{ + + int retValue = OK ; + + + + matrixPtr->Divider = ((screenPtr[0].x - screenPtr[2].x) * (screenPtr[1].y - screenPtr[2].y)) - + ((screenPtr[1].x - screenPtr[2].x) * (screenPtr[0].y - screenPtr[2].y)) ; + + if( matrixPtr->Divider == 0 ) + { + retValue = NOT_OK ; + } + else + { + matrixPtr->An = ((displayPtr[0].x - displayPtr[2].x) * (screenPtr[1].y - screenPtr[2].y)) - + ((displayPtr[1].x - displayPtr[2].x) * (screenPtr[0].y - screenPtr[2].y)) ; + + matrixPtr->Bn = ((screenPtr[0].x - screenPtr[2].x) * (displayPtr[1].x - displayPtr[2].x)) - + ((displayPtr[0].x - displayPtr[2].x) * (screenPtr[1].x - screenPtr[2].x)) ; + + matrixPtr->Cn = (screenPtr[2].x * displayPtr[1].x - screenPtr[1].x * displayPtr[2].x) * screenPtr[0].y + + (screenPtr[0].x * displayPtr[2].x - screenPtr[2].x * displayPtr[0].x) * screenPtr[1].y + + (screenPtr[1].x * displayPtr[0].x - screenPtr[0].x * displayPtr[1].x) * screenPtr[2].y ; + + matrixPtr->Dn = ((displayPtr[0].y - displayPtr[2].y) * (screenPtr[1].y - screenPtr[2].y)) - + ((displayPtr[1].y - displayPtr[2].y) * (screenPtr[0].y - screenPtr[2].y)) ; + + matrixPtr->En = ((screenPtr[0].x - screenPtr[2].x) * (displayPtr[1].y - displayPtr[2].y)) - + ((displayPtr[0].y - displayPtr[2].y) * (screenPtr[1].x - screenPtr[2].x)) ; + + matrixPtr->Fn = (screenPtr[2].x * displayPtr[1].y - screenPtr[1].x * displayPtr[2].y) * screenPtr[0].y + + (screenPtr[0].x * displayPtr[2].y - screenPtr[2].x * displayPtr[0].y) * screenPtr[1].y + + (screenPtr[1].x * displayPtr[0].y - screenPtr[0].x * displayPtr[1].y) * screenPtr[2].y ; + } + + return( retValue ) ; + +} /* end of setCalibrationMatrix() */ + + + +/********************************************************************** + * + * Function: getDisplayPoint() + * + * Description: Given a valid set of calibration factors and a point + * value reported by the touch screen, this function + * calculates and returns the true (or closest to true) + * display point below the spot where the touch screen + * was touched. + * + * + * + * Argument(s): displayPtr (output) - Pointer to the calculated + * (true) display point. + * screenPtr (input) - Pointer to the reported touch + * screen point. + * matrixPtr (input) - Pointer to calibration factors + * matrix previously calculated + * from a call to + * setCalibrationMatrix() + * + * + * The function simply solves for Xd and Yd by implementing the + * computations required by the translation matrix. + * + * /- -\ + * /- -\ /- -\ | | + * | | | | | Xs | + * | Xd | | A B C | | | + * | | = | | * | Ys | + * | Yd | | D E F | | | + * | | | | | 1 | + * \- -/ \- -/ | | + * \- -/ + * + * It must be kept brief to avoid consuming CPU cycles. + * + * + * Return: OK - the display point was correctly calculated + * and its value is in the output argument. + * NOT_OK - an error was detected and the function + * failed to return a valid point. + * + * + * + * NOTE! NOTE! NOTE! + * + * setCalibrationMatrix() and getDisplayPoint() will do fine + * for you as they are, provided that your digitizer + * resolution does not exceed 10 bits (1024 values). Higher + * resolutions may cause the integer operations to overflow + * and return incorrect values. If you wish to use these + * functions with digitizer resolutions of 12 bits (4096 + * values) you will either have to a) use 64-bit signed + * integer variables and math, or b) judiciously modify the + * operations to scale results by a factor of 2 or even 4. + * + * + */ +int getDisplayPoint( POINT * displayPtr, + POINT * screenPtr, + MATRIX * matrixPtr ) +{ + int retValue = OK ; + + + if( matrixPtr->Divider != 0 ) + { + + /* Operation order is important since we are doing integer */ + /* math. Make sure you add all terms together before */ + /* dividing, so that the remainder is not rounded off */ + /* prematurely. */ + + displayPtr->x = ( (matrixPtr->An * screenPtr->x) + + (matrixPtr->Bn * screenPtr->y) + + matrixPtr->Cn + ) / matrixPtr->Divider ; + + displayPtr->y = ( (matrixPtr->Dn * screenPtr->x) + + (matrixPtr->En * screenPtr->y) + + matrixPtr->Fn + ) / matrixPtr->Divider ; + } + else + { + retValue = NOT_OK ; + } + + return( retValue ) ; + +} /* end of getDisplayPoint() */ + + diff --git a/calibrate.h b/calibrate.h new file mode 100755 index 0000000..ad34618 --- /dev/null +++ b/calibrate.h @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) 2001, Carlos E. Vidales. All rights reserved. + * + * This sample program was written and put in the public domain + * by Carlos E. Vidales. The program is provided "as is" + * without warranty of any kind, either expressed or implied. + * If you choose to use the program within your own products + * you do so at your own risk, and assume the responsibility + * for servicing, repairing or correcting the program should + * it prove defective in any manner. + * You may copy and distribute the program's source code in any + * medium, provided that you also include in each copy an + * appropriate copyright notice and disclaimer of warranty. + * You may also modify this program and distribute copies of + * it provided that you include prominent notices stating + * that you changed the file(s) and the date of any change, + * and that you do not charge any royalties or licenses for + * its use. + * + * + * File Name: calibrate.h + * + * + * Definition of constants and structures, and declaration of functions + * in Calibrate.c + * + * Revisions: Kevin Peck 2019 + */ +#ifdef __cplusplus +//extern "C" { +#endif +#ifndef _CALIBRATE_H_ + +#define _CALIBRATE_H_ + +/****************************************************/ +/* */ +/* Included files */ +/* */ +/****************************************************/ + +#include + + +/****************************************************/ +/* */ +/* Definitions */ +/* */ +/****************************************************/ + +#ifndef _CALIBRATE_C_ + #define EXTERN extern +#else + #define EXTERN +#endif + + + +#ifndef OK + #define OK 0 + #define NOT_OK -1 +#endif + + + +#define INT32 long + + + + +/****************************************************/ +/* */ +/* Structures */ +/* */ +/****************************************************/ + + +typedef struct Point { + INT32 x,y ; + } POINT ; + + + +typedef struct Matrix { + /* This arrangement of values facilitates + * calculations within getDisplayPoint() + */ + INT32 An, /* A = An/Divider */ + Bn, /* B = Bn/Divider */ + Cn, /* C = Cn/Divider */ + Dn, /* D = Dn/Divider */ + En, /* E = En/Divider */ + Fn, /* F = Fn/Divider */ + Divider ; + } MATRIX ; + + + + +/****************************************************/ +/* */ +/* Function declarations */ +/* */ +/****************************************************/ + + +EXTERN int setCalibrationMatrix( POINT * display, + POINT * screen, + MATRIX * matrix) ; + + +EXTERN int getDisplayPoint( POINT * display, + POINT * screen, + MATRIX * matrix ) ; + + +#endif /* _CALIBRATE_H_ */ + +#ifdef __cplusplus +//} +#endif diff --git a/config.h b/config.h index 75b802b..b519fc5 100644 --- a/config.h +++ b/config.h @@ -33,7 +33,7 @@ // output new frames at this vsync-detached interval, so there's a 50 Hz vs 60 Hz mismatch that results // in visible microstuttering. Still, providing this as an option, this might be good for content that // is known to run at native 60Hz. -// #define USE_GPU_VSYNC +//#define USE_GPU_VSYNC // Always enable GPU VSync on the Pi Zero. Even though it is suboptimal and can cause stuttering, it saves battery. #if defined(SINGLE_CORE_BOARD) @@ -196,7 +196,7 @@ // #define USE_DMA_TRANSFERS // If defined, enables code to manage the backlight. -// #define BACKLIGHT_CONTROL +#define BACKLIGHT_CONTROL #if defined(BACKLIGHT_CONTROL) @@ -212,6 +212,10 @@ #define TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY (1 * 60 * 1000000) #endif +// +// Sleep for up to 1 / N of a second. With sendNoOpCommand() to Display, the side effect is sampling +// the touch screen N times per second. +#define SLEEP_TIME_USECS_NODRAWING (1) // If less than this much % of the screen changes per frame, the screen is considered to be inactive, and // the display backlight can automatically turn off, if TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY is diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..ad29cc2 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +fbcp-ili9341 for Debian +---------------------- + +https://github.com/kpishere/fbcp-ili9341 + + -- kunzol Sat, 05 Oct 2019 18:24:59 +0200 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..3a54d7f --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +fbcp-ili9341 (0.1) unstable; urgency=medium + + * Initial Release. + + -- kunzol Sat, 05 Oct 2019 18:24:59 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..2a13aac --- /dev/null +++ b/debian/control @@ -0,0 +1,20 @@ +Source: fbcp-ili9341 +Section: misc +Priority: optional +Maintainer: kunzol +Build-Depends: debhelper (>= 9), dkms, config-package-dev, debhelper +Standards-Version: 3.9.8 +Homepage: https://github.com/kpishere/fbcp-ili9341 +#Vcs-Git: https://anonscm.debian.org/collab-maint/fbcp-ili9341.git +#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/fbcp-ili9341.git + +Package: fbcp-ili9341 +Architecture: armhf +Depends: ${shlibs:Depends}, ${misc:Depends}, libc6, raspberrypi-kernel-headers, cmake, g++, make, binutils, kmod, libraspberrypi0, crudini, procps +Description: SPI-based LCD displays for Raspberry Pi + This repository implements a driver for + certain SPI-based LCD displays for + Raspberry Pi A, B, 2, 3 and Zero + via dkms. + . + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..87e2491 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,31 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: fbcp-ili9341 +Source: + +Files: * +Copyright: 2018 Jukka Jylänki +License: MIT + +Files: debian/* +Copyright: 2019 kunzol https://github.com/Kunzol +License: MIT + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/debian/fbcp-ili9341-module b/debian/fbcp-ili9341-module new file mode 100755 index 0000000..3f5169b --- /dev/null +++ b/debian/fbcp-ili9341-module @@ -0,0 +1,65 @@ +#!/bin/bash +command="${1}" +shift +fbcpmodopt="$*" +fbcplog="/var/log/fbcp-ili9341-dkms.log" +fbcpmod="bcm2835_spi_display" +fbcpcmd="fbcp-ili9341" +fbcpscript="$( readlink -f $0 )" + +function fbcp_log() { + logger -e -t "${fbcpcmd}" -p user.info -- "$$ $*" +} + +case "${command,,}" in + run) + fbcp_log "${command}" + while IFS= read -r fbcp + do + fbcp_log "${fbcp}" + done < <( /usr/sbin/${fbcpcmd} ) & + ;; + start) + fbcp_log "${command}" + fbcp_log "$( modprobe -v --ignore-install ${fbcpmod} ${fbcpmodopt} )" +# module loaded and fbcpcmd not running + if lsmod | grep -q ${fbcpmod} && pgrep -v ${fbcpcmd} > /dev/null ; then + exec ${fbcpscript} run + fi + ;; + stop) + fbcp_log "${command}" +# try killing fbcpcmd not more than 7 times + declare -i i=0 + while pgrep -x ${fbcpcmd} > /dev/null && [ ${i} -lt 7 ] + do + pkill -x -TERM ${fbcpcmd} + if pgrep -x ${fbcpcmd} > /dev/null; then + ((i++)) + sleep 1 + else + break + fi + done + +# force kill fbcpcmd + pgrep -x ${fbcpcmd} > /dev/null && pkill -x -KILL ${fbcpcmd} || true + + if pgrep -x ${fbcpcmd} > /dev/null; then + echo "fbcp-ili9341 still running. can not unload module." >&2 + exit 1 + else + modprobe -r --ignore-remove ${fbcpmod} + fi + ;; + status) + pgrep -x -l ${fbcpcmd} || echo "fbcp-ili9341 not running." + lsmod | grep ${fbcpmod} || echo "${fbcpmod} not loaded." + ;; + *) + echo "usage: $0 start|stop|status" >&2 + exit 1 + ;; +esac + +fbcp_log "${command} end" diff --git a/debian/fbcp-ili9341-module.1 b/debian/fbcp-ili9341-module.1 new file mode 100644 index 0000000..42352cc --- /dev/null +++ b/debian/fbcp-ili9341-module.1 @@ -0,0 +1,20 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" (C) Copyright 2019 kunzol , +.\" +.TH Fbcp-ili9341-module 1 "October 5 2019" +.SH NAME +fbcp-ili9341-module \- start and stop fbcp-ili9341 +.SH SYNOPSIS +.B fbcp-ili9341-module +.RI (start | stop) +.SH DESCRIPTION +Start or stop +.B fbcp-ili9341 +.PP +.SH OPTIONS +.TP +.B stop +start fbcp-ili9341 including loading module into kernel. +.TP +.B stop +stop fbcp-ili9341 including unloading module from kernel. diff --git a/debian/fbcp-ili9341.conf b/debian/fbcp-ili9341.conf new file mode 100644 index 0000000..da3d234 --- /dev/null +++ b/debian/fbcp-ili9341.conf @@ -0,0 +1,4 @@ +# +# fbcp-ili9341 +# +bcm2835_spi_display diff --git a/debian/fbcp-ili9341.config b/debian/fbcp-ili9341.config new file mode 100644 index 0000000..3db5e1b --- /dev/null +++ b/debian/fbcp-ili9341.config @@ -0,0 +1,52 @@ +#!/bin/bash +# vim:syntax=sh:ai:et:si:sts=4:sw=4 +# +# config script for fbcp-ili9341 +# +# see: dh_installdeb(1) + +set -e + +. /usr/share/debconf/confmodule + +case "$1" in + configure|reconfigure) + db_input critical fbcp-ili9341/ishat || true + db_go || true + + db_get fbcp-ili9341/ishat || true + ishat="${RET}" + + case "${ishat,,}" in + true) + db_input critical fbcp-ili9341/hat || true + db_go || true + + ;; + false) + db_input critical fbcp-ili9341/wired || true + db_go || true + ;; + *) + exit 1 + ;; + esac + + db_input critical fbcp-ili9341/divisor || true + db_go || true + db_input high fbcp-ili9341/statistics || true + db_go || true + db_input high fbcp-ili9341/hdmi || true + db_go || true + ;; + + *) + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/fbcp-ili9341.dirs b/debian/fbcp-ili9341.dirs new file mode 100644 index 0000000..592a9d9 --- /dev/null +++ b/debian/fbcp-ili9341.dirs @@ -0,0 +1,2 @@ +/usr/share/fbcp-ili9341 +/usr/sbin diff --git a/debian/fbcp-ili9341.dkms b/debian/fbcp-ili9341.dkms new file mode 100644 index 0000000..753fed1 --- /dev/null +++ b/debian/fbcp-ili9341.dkms @@ -0,0 +1,9 @@ +PACKAGE_NAME="fbcp-ili9341" +PACKAGE_VERSION=#MODULE_VERSION# +#BUILD_EXCLUSIVE_ARCH="armhf" +BUILT_MODULE_NAME[0]="bcm2835_spi_display" +MAKE[0]="cd kernel; KDIR=${install_tree}/${kernelver} make; cd .." +BUILT_MODULE_LOCATION[0]="kernel/" +CLEAN="cd kernel; KDIR=${install_tree}/${kernelver} make clean; cd .." +DEST_MODULE_LOCATION[0]=/extra +AUTOINSTALL=yes diff --git a/debian/fbcp-ili9341.lintian-overrides b/debian/fbcp-ili9341.lintian-overrides new file mode 100644 index 0000000..6c3a75f --- /dev/null +++ b/debian/fbcp-ili9341.lintian-overrides @@ -0,0 +1 @@ +fbcp-ili9341: extra-license-file usr/share/fbcp-ili9341/LICENSE.txt diff --git a/debian/fbcp-ili9341.manpages b/debian/fbcp-ili9341.manpages new file mode 100644 index 0000000..723b559 --- /dev/null +++ b/debian/fbcp-ili9341.manpages @@ -0,0 +1,3 @@ +debian/fbcp-ili9341-module.1 +debian/tcTest.1 +debian/tcCalib.1 diff --git a/debian/fbcp-ili9341.modprobe b/debian/fbcp-ili9341.modprobe new file mode 100644 index 0000000..38883a1 --- /dev/null +++ b/debian/fbcp-ili9341.modprobe @@ -0,0 +1,3 @@ + +install bcm2835_spi_display /usr/sbin/fbcp-ili9341-module start $CMDLINE_OPTS +remove bcm2835_spi_display /usr/sbin/fbcp-ili9341-module stop diff --git a/debian/fbcp-ili9341.postinst b/debian/fbcp-ili9341.postinst new file mode 100644 index 0000000..9657b1c --- /dev/null +++ b/debian/fbcp-ili9341.postinst @@ -0,0 +1,133 @@ +#!/bin/bash +# vim:syntax=sh:ai:et:si:sts=4:sw=4 +# +# postinst script for fbcp-ili9341 +# + +set -e +. /usr/share/debconf/confmodule + +# list of display resolution +declare -A displays=( +[ILI9341]="320 240 60 1 0 0 0" +[ILI9340]="320 240 60 1 0 0 0" +[HX8357D]="480 320 60 1 0 0 0" +[SSD1351]="320 240 1 0 0 0" +[ILI9486]="320 480 60 1 0 0 0" +[ILI9486L]="320 240 60 1 0 0 0" +[ST7735R]="320 240 60 1 0 0 0" +[MZ61581]="320 240 60 1 0 0 0" +[ST7789]="320 240 60 1 0 0 0" +[ST7789VW]="320 240 60 1 0 0 0" +[ST7735S]="320 240 60 1 0 0 0" +[MPI3501]="320 240 60 1 0 0 0" +[ADAFRUIT_ILI9341_PITFT]="320 240 60 1 0 0 0" +[ADAFRUIT_HX8357D_PITFT]="480 320 60 1 0 0 0" +[FREEPLAYTECH_WAVESHARE32B]="480 320 60 1 0 0 0" +[WAVESHARE35B_ILI9486]="320 480 60 1 0 0 0" +[TONTEC_MZ61581]="320 480 60 1 0 0 0" +[WAVESHARE_ST7789VW_HAT]="240 240 60 1 0 0 0" +[WAVESHARE_ST7735S_HAT]="128 128 60 1 0 0 0" +[KEDEI_V63_MPI3501]="480 320 60 1 0 0 0" +) + +sourcedir="/usr/share/${DPKG_MAINTSCRIPT_PACKAGE}/build" +sharedir="/usr/share/${DPKG_MAINTSCRIPT_PACKAGE}" + +case "$1" in + configure) + declare -a cmakeopt=( ) + + db_get fbcp-ili9341/statistics || true + statistics="${RET}" + case "${statistics,,}" in + 0|1|2) + cmakeopt+=( "-DSTATISTICS=${statistics}" ) + ;; + *) + cmakeopt+=( "-DSTATISTICS=0" ) + ;; + esac + +# get the controller from the selection + db_get fbcp-ili9341/ishat || true + ishat="${RET}" + case "${ishat,,}" in + true) + db_get fbcp-ili9341/hat || true + controller="${RET}" + ;; + false) + db_get fbcp-ili9341/wired || true + controller="${RET}" + ;; + esac + cmakeopt+=( "-D${controller}=ON" ) + +# get the divisor from the selection + db_get fbcp-ili9341/divisor || true + declare -i divisor="${RET}" + if [ ${divisor} -eq 0 ]; then + divisor=30 + elif [ $(( divisor % 2 )) -gt 0 ]; then +# use next higher even number + (( divisor++ )) + fi + cmakeopt+=( "-DSPI_BUS_CLOCK_DIVISOR=${RET}" ) + +# build the binary + rm -rf -- "${sourcedir}" + mkdir -pv "${sourcedir}" + pushd "${sourcedir}" > /dev/null 2>&1 + echo "cmake ${cmakeopt[@]}" >&2 + cmake ${cmakeopt[@]} .. + make + if [ -f "fbcp-ili9341" ]; then + cp -vfT "fbcp-ili9341" "/usr/sbin/fbcp-ili9341" + else + echo "could not build fbcp-ili9341 binary." >&2 + echo "check for the reason in ${sourcedir}" >&2 + exit 1 + fi + make clean + popd > /dev/null 2>&1 + +# change config.txt + db_get fbcp-ili9341/hdmi || true + hdmi="${RET}" + case "${hdmi,,}" in + true) + cp -f --backup=numbered "/boot/config.txt" "/var/backups/config.txt" + cp -f "/boot/config.txt" "${sharedir}/config.txt.new" + crudini --set "${sharedir}/config.txt.new" "" hdmi_group 2 + crudini --set "${sharedir}/config.txt.new" "" hdmi_mode 87 + crudini --set "${sharedir}/config.txt.new" "" hdmi_cvt "${displays[${controller}]:-320 240 60 1 0 0 0}" + crudini --set "${sharedir}/config.txt.new" "" hdmi_force_hotplug 1 + cp -f "${sharedir}/config.txt.new" "/boot/config.txt.fbcp" + ;; + false) + ;; + *) + echo "Ignoring HDMI value: ${hdmi}" >&2 + ;; + esac + + cp -nv "/boot/config.txt" "/boot/config.txt.fbcp-orig" + cp -fv "/boot/config.txt.fbcp" "/boot/config.txt" + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/fbcp-ili9341.postrm b/debian/fbcp-ili9341.postrm new file mode 100644 index 0000000..d5a75d9 --- /dev/null +++ b/debian/fbcp-ili9341.postrm @@ -0,0 +1,33 @@ +#!/bin/sh +# postrm script for fbcp-ili9341 +# +# see: dh_installdeb(1) + +set -e +. /usr/share/debconf/confmodule + +sharedir="/usr/share/${DPKG_MAINTSCRIPT_PACKAGE}" + +case "$1" in + purge) + rm -f "/boot/config.txt.fbcp" + rm -f "/usr/sbin/fbcp-ili9341" + ;; + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + rm -f "${sharedir}/config.txt.new" + rm -f "/usr/sbin/fbcp-ili9341" + rm -rf "${sharedir}/build" + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/fbcp-ili9341.prerm b/debian/fbcp-ili9341.prerm new file mode 100644 index 0000000..f05df39 --- /dev/null +++ b/debian/fbcp-ili9341.prerm @@ -0,0 +1,34 @@ +#!/bin/sh +# prerm script for fbcp-ili9341 +# +# see: dh_installdeb(1) + +set -e + +case "$1" in + remove|upgrade|deconfigure) + if [ -f "/usr/sbin/fbcp-ili9341-module" ]; then + "/usr/sbin/fbcp-ili9341-module" stop + fi + if [ -f "/boot/config.txt.fbcp-orig" ]; then + rm -fv "/boot/config.txt" && mv -v "/boot/config.txt.fbcp-orig" "/boot/config.txt" + else + echo "/boot/config.txt.fbcp-orig not found." >&2 + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/fbcp-ili9341.templates b/debian/fbcp-ili9341.templates new file mode 100644 index 0000000..fd6cbe8 --- /dev/null +++ b/debian/fbcp-ili9341.templates @@ -0,0 +1,42 @@ +Template: fbcp-ili9341/ishat +Type: boolean +Default: true +Description: Is your controller stacked on top of the Pi? + +Template: fbcp-ili9341/hat +Type: select +Choices: ADAFRUIT_ILI9341_PITFT, ADAFRUIT_HX8357D_PITFT, FREEPLAYTECH_WAVESHARE32B, WAVESHARE35B_ILI9486, TONTEC_MZ61581, WAVESHARE_ST7789VW_HAT, WAVESHARE_ST7735S_HAT, KEDEI_V63_MPI3501 +Description: Which type of LCD display: + see the list at: + https://github.com/kpishere/fbcp-ili9341 + +Template: fbcp-ili9341/wired +Type: select +Choices: ILI9341, ILI9340, HX8357D, SSD1351, ILI9486, ILI9486L, ST7735R, MZ61581, ST7789, ST7789VW, ST7735S, MPI3501 +Description: Which type of LCD display: + see the list at: + https://github.com/kpishere/fbcp-ili9341 + +Template: fbcp-ili9341/divisor +Type: string +Default: 12 +Description: Enter divisor of the core_freq: + This must be an even number. + Sets the clock divisor number used with + core_freq from config.txt. + +Template: fbcp-ili9341/statistics +Type: select +Choices: 0, 1, 2 +Default: 0 +Description: Show statistics overlay? + Enable this to show a statistics overlay on + top of the screen. + The higher the number the more is shown. + . + +Template: fbcp-ili9341/hdmi +Type: boolean +Default: true +Description: Configure config.txt for hdmi size? + diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..1e595a6 --- /dev/null +++ b/debian/rules @@ -0,0 +1,50 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + +include /usr/share/dpkg/default.mk + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +%: + dh $@ --with dkms + +override_dh_auto_install: + cp -f debian/$(DEB_SOURCE)-module debian/$(DEB_SOURCE)/usr/sbin/$(DEB_SOURCE)-module + install -d debian/$(DEB_SOURCE)/etc/modules-load.d/ + cp -f debian/$(DEB_SOURCE).conf debian/$(DEB_SOURCE)/etc/modules-load.d/ + install -d debian/$(DEB_SOURCE)/usr/src/$(DEB_SOURCE)-$(DEB_VERSION) + cp -f spi.h debian/$(DEB_SOURCE)/usr/src/$(DEB_SOURCE)-$(DEB_VERSION) + mv -f util/tcTest debian/$(DEB_SOURCE)/usr/sbin/ + mv -f util/tcCalib debian/$(DEB_SOURCE)/usr/sbin/ + + +override_dh_install: + dh_install -Xdebian -X.git -X.o * usr/share/$(DEB_SOURCE) + dh_install -X.sh kernel usr/src/$(DEB_SOURCE)-$(DEB_VERSION) + find debian/$(DEB_SOURCE)/usr/share/$(DEB_SOURCE) -type f -perm -5 -print0 2>/dev/null | xargs -0r chmod a-X + +override_dh_dkms: + dh_dkms -V "${DEB_VERSION}" + +# Configure and compile util +override_dh_auto_configure: + dh_auto_configure --builddirectory=util --buildsystem=makefile -- + +override_dh_auto_build: + dh_auto_build --builddirectory=util --buildsystem=makefile -- + +override_dh_strip: + strip debian/$(DEB_SOURCE)/usr/sbin/tcTest + strip debian/$(DEB_SOURCE)/usr/sbin/tcCalib + +override_dh_makeshlibs override_dh_shlibdeps: diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/tcCalib.1 b/debian/tcCalib.1 new file mode 100644 index 0000000..6189d91 --- /dev/null +++ b/debian/tcCalib.1 @@ -0,0 +1,10 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" (C) Copyright 2019 kunzol , +.\" +.TH tcCalib 1 "October 5 2019" +.SH NAME +tcCalib \- calibrate touchscreen +.SH SYNOPSIS +.B tcCalib +.SH DESCRIPTION +With this program you calibrate the touchscreen. diff --git a/debian/tcTest.1 b/debian/tcTest.1 new file mode 100644 index 0000000..faf9201 --- /dev/null +++ b/debian/tcTest.1 @@ -0,0 +1,11 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" (C) Copyright 2019 kunzol , +.\" +.TH tcTest 1 "October 5 2019" +.SH NAME +tcTest \- test display +.SH SYNOPSIS +.B tcTest +.SH DESCRIPTION +With this program you send test patterns to +the display. diff --git a/diff.cpp b/diff.cpp index 8966a15..54e711f 100644 --- a/diff.cpp +++ b/diff.cpp @@ -3,7 +3,7 @@ #include "util.h" #include "display.h" #include "gpu.h" -#include "spi.h" +#include "spi_user.h" Span *spans = 0; diff --git a/display.cpp b/display.cpp index 48787f8..c33e212 100644 --- a/display.cpp +++ b/display.cpp @@ -1,6 +1,6 @@ #include "config.h" #include "display.h" -#include "spi.h" +#include "spi_user.h" #include diff --git a/dma.cpp b/dma.cpp index d2844e2..3084f91 100644 --- a/dma.cpp +++ b/dma.cpp @@ -5,11 +5,13 @@ #include // uint32_t #include // syslog #include // mmap, munmap, PROT_READ, PROT_WRITE +#include "spi_kernel.h" +#else +#include "spi_user.h" #endif #include "config.h" #include "dma.h" -#include "spi.h" #include "gpu.h" #include "util.h" #include "mailbox.h" diff --git a/fbcp-ili9341.cpp b/fbcp-ili9341.cpp index a7d324f..be5191f 100644 --- a/fbcp-ili9341.cpp +++ b/fbcp-ili9341.cpp @@ -18,7 +18,7 @@ #include "config.h" #include "text.h" -#include "spi.h" +#include "spi_user.h" #include "gpu.h" #include "statistics.h" #include "tick.h" @@ -92,9 +92,11 @@ int main() { signal(SIGINT, ProgramInterruptHandler); signal(SIGQUIT, ProgramInterruptHandler); - signal(SIGUSR1, ProgramInterruptHandler); + // This signal is used by XPT2046 class as signal to re-load config file + //signal(SIGUSR1, ProgramInterruptHandler); signal(SIGUSR2, ProgramInterruptHandler); signal(SIGTERM, ProgramInterruptHandler); + #ifdef RUN_WITH_REALTIME_THREAD_PRIORITY SetRealtimeThreadPriority(); #endif @@ -139,6 +141,7 @@ int main() printf("All initialized, now running main loop...\n"); while(programRunning) { + sendNoOpCommand(); prevFrameWasInterlacedUpdate = interlacedUpdate; // If last update was interlaced, it means we still have half of the image pending to be updated. In such a case, @@ -150,7 +153,10 @@ int main() #ifdef THROTTLE_INTERLACING timespec timeout = {}; timeout.tv_nsec = 1000 * MIN(1000000, MAX(1, 750/*0.75ms extra sleep so we know we should likely sleep long enough to see the next frame*/ + PredictNextFrameArrivalTime() - tick())); - if (programRunning) syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, &timeout, 0, 0); // Start sleeping until we get new tasks + if (programRunning) + { + syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, &timeout, 0, 0); // Start sleeping until we get new tasks + } #endif // If THROTTLE_INTERLACING is not defined, we'll fall right through and immediately submit the rest of the remaining content on screen to attempt to minimize the visual // observable effect of interlacing, although at the expense of smooth animation (falling through here causes jitter) @@ -160,6 +166,7 @@ int main() uint64_t waitStart = tick(); while(__atomic_load_n(&numNewGpuFrames, __ATOMIC_SEQ_CST) == 0) { + sendNoOpCommand(); #if defined(BACKLIGHT_CONTROL) && defined(TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY) if (!displayOff && tick() - waitStart > TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY) { @@ -170,18 +177,30 @@ int main() if (!displayOff) { timespec timeout = {}; - timeout.tv_sec = ((uint64_t)TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY * 1000) / 1000000000; - timeout.tv_nsec = ((uint64_t)TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY * 1000) % 1000000000; - if (programRunning) syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, &timeout, 0, 0); // Sleep until the next frame arrives + timeout.tv_nsec = ((uint64_t)SLEEP_TIME_USECS_NODRAWING * 1000) % 1000000000; + if (programRunning) + { + // Sleep until the next frame arrives or up to SLEEP_TIME_USECS_NODRAWING time + syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, &timeout, 0, 0); + } } - else + else #endif - if (programRunning) syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, 0, 0, 0); // Sleep until the next frame arrives + { + if (programRunning) + { + timespec timeout = {}; + timeout.tv_nsec = ((uint64_t)SLEEP_TIME_USECS_NODRAWING * 1000) % 1000000000; + // Sleep until the next frame arrives or up to SLEEP_TIME_USECS_NODRAWING time + syscall(SYS_futex, &numNewGpuFrames, FUTEX_WAIT, 0, &timeout, 0, 0); + } + } } } - bool spiThreadWasWorkingHardBefore = false; + sendNoOpCommand(); + // At all times keep at most two rendered frames in the SPI task queue pending to be displayed. Only proceed to submit a new frame // once the older of those has been displayed. bool once = true; @@ -246,25 +265,25 @@ int main() if (gotNewFramebuffer) { #ifdef USE_GPU_VSYNC - // TODO: Hardcoded vsync interval to 60 for now. Would be better to compute yet another histogram of the vsync arrival times, if vsync is not set to 60hz. - // N.B. copying directly to videoCoreFramebuffer[1] that may be directly accessed by the main thread, so this could - // produce a visible tear between two adjacent frames, but since we don't have vsync anyways, currently not caring too much. +// TODO: Hardcoded vsync interval to 60 for now. Would be better to compute yet another histogram of the vsync arrival times, if vsync is not set to 60hz. +// N.B. copying directly to videoCoreFramebuffer[1] that may be directly accessed by the main thread, so this could +// produce a visible tear between two adjacent frames, but since we don't have vsync anyways, currently not caring too much. frameObtainedTime = tick(); uint64_t framePollingStartTime = frameObtainedTime; -#if defined(SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES) || defined(SAVE_BATTERY_BY_SLEEPING_WHEN_IDLE) + #if defined(SAVE_BATTERY_BY_PREDICTING_FRAME_ARRIVAL_TIMES) || defined(SAVE_BATTERY_BY_SLEEPING_WHEN_IDLE) uint64_t nextFrameArrivalTime = PredictNextFrameArrivalTime(); int64_t timeToSleep = nextFrameArrivalTime - tick(); - if (timeToSleep > 0) + if (timeToSleep > 0) { usleep(timeToSleep); -#endif + } + #endif framebufferHasNewChangedPixels = SnapshotFramebuffer(framebuffer[0]); #else memcpy(framebuffer[0], videoCoreFramebuffer[1], gpuFramebufferSizeBytes); #endif - #ifdef STATISTICS uint64_t now = tick(); for(int i = 0; i < numNewFrames - 1 && frameSkipTimeHistorySize < FRAMERATE_HISTORY_LENGTH; ++i) @@ -273,17 +292,16 @@ int main() __atomic_fetch_sub(&numNewGpuFrames, numNewFrames, __ATOMIC_SEQ_CST); DrawStatisticsOverlay(framebuffer[0]); - #ifdef USE_GPU_VSYNC -#ifdef STATISTICS + #ifdef STATISTICS uint64_t completelyUnnecessaryTimeWastedPollingGPUStart = tick(); -#endif + #endif // DispmanX PROBLEM! When latching onto the vsync signal, the DispmanX API sends the signal at arbitrary phase with respect to the application actually producing its frames. // Therefore even while we do get a smooth 16.666.. msec interval vsync signal, we have no idea whether the application has actually produced a new frame at that time. Therefore // we must keep polling for frames until we find one that it has produced. -#ifdef SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES + #ifdef SELF_SYNCHRONIZE_TO_GPU_VSYNC_PRODUCED_NEW_FRAMES framebufferHasNewChangedPixels = framebufferHasNewChangedPixels && IsNewFramebuffer(framebuffer[0], framebuffer[1]); uint64_t timeToGiveUpThereIsNotGoingToBeANewFrame = framePollingStartTime + 1000000/TARGET_FRAME_RATE/2; while(!framebufferHasNewChangedPixels && tick() < timeToGiveUpThereIsNotGoingToBeANewFrame) @@ -294,21 +312,21 @@ int main() DrawStatisticsOverlay(framebuffer[0]); framebufferHasNewChangedPixels = framebufferHasNewChangedPixels && IsNewFramebuffer(framebuffer[0], framebuffer[1]); } -#else + #else framebufferHasNewChangedPixels = true; -#endif + #endif numNewFrames = __atomic_load_n(&numNewGpuFrames, __ATOMIC_SEQ_CST); __atomic_fetch_sub(&numNewGpuFrames, numNewFrames, __ATOMIC_SEQ_CST); -#ifdef STATISTICS + #ifdef STATISTICS now = tick(); for(int i = 0; i < numNewFrames - 1 && frameSkipTimeHistorySize < FRAMERATE_HISTORY_LENGTH; ++i) frameSkipTimeHistory[frameSkipTimeHistorySize++] = now; uint64_t completelyUnnecessaryTimeWastedPollingGPUStop = tick(); __atomic_fetch_add(&timeWastedPollingGPU, completelyUnnecessaryTimeWastedPollingGPUStop-completelyUnnecessaryTimeWastedPollingGPUStart, __ATOMIC_RELAXED); -#endif + #endif #else // !USE_GPU_VSYNC if (!displayOff) @@ -528,7 +546,6 @@ int main() prevFrameEnd = curFrameEnd; curFrameEnd = spiTaskMemory->queueTail; } - #if defined(BACKLIGHT_CONTROL) && defined(TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY) double percentageOfScreenChanged = (double)numChangedPixels/(DISPLAY_DRAWABLE_WIDTH*DISPLAY_DRAWABLE_HEIGHT); bool displayIsActive = percentageOfScreenChanged > DISPLAY_CONSIDERED_INACTIVE_PERCENTAGE; @@ -536,7 +553,7 @@ int main() displayContentsLastChanged = tick(); bool keyboardIsActive = TimeSinceLastKeyboardPress() < TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY; - if (displayIsActive || keyboardIsActive) + if (displayIsActive || keyboardIsActive || activeTouchscreen() ) { if (displayOff) { diff --git a/hx8357d.cpp b/hx8357d.cpp index 5d9562c..e86d1c3 100644 --- a/hx8357d.cpp +++ b/hx8357d.cpp @@ -4,7 +4,7 @@ #ifdef HX8357D -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/ili9341.cpp b/ili9341.cpp index 5af3363..823ac68 100644 --- a/ili9341.cpp +++ b/ili9341.cpp @@ -2,7 +2,7 @@ #if defined(ILI9341) || defined(ILI9340) -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/ili9486.cpp b/ili9486.cpp index 5c52a72..0908196 100644 --- a/ili9486.cpp +++ b/ili9486.cpp @@ -2,7 +2,7 @@ #if defined(ILI9486) || defined(ILI9486L) -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/kernel/Makefile b/kernel/Makefile index 6ffe5c1..be8ba68 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,3 +1,36 @@ -obj-m := bcm2835_spi_display.o +obj-m:= bcm2835_spi_display.o -CFLAGS_bcm2835_spi_display.o := -O3 -std=gnu99 -Wno-declaration-after-statement -DKERNEL_MODULE -DILI9341=1 -DADAFRUIT_ILI9341_PITFT=1 +CFLAGS_bcm2835_spi_display.o := -O3 -std=gnu99 -Wno-declaration-after-statement -DKERNEL_MODULE=1 -DILI9341=1 -DADAFRUIT_ILI9341_PITFT=1 -DSPI_BUS_CLOCK_DIVISOR=48 + +KDIR ?= /lib/modules/$(shell uname -r) + +all $(obj-m:%.o=%.ko): + $(MAKE) -C "$(KDIR)/build" M="$(CURDIR)" modules + +install: stop $(obj-m:%.o=%.ko) + $(MAKE) -C "$(KDIR)/build" M="$(CURDIR)" modules_install + @depmod -a + +uninstall: stop + @rm -vf -- "$(KDIR)/extra/$(obj-m:%.o=%.ko)" + @depmod -a + +start: + @modprobe -v $(obj-m:%.o=%) + +stop: + @pgrep "fbcp-ili9341.*" && pkill "fbcp-ili9341.*" || true + @pgrep "fbcp-ili9341.*" && sleep 5 && pkill -KILL "fbcp-ili9341.*" || true + @modprobe -vr $(obj-m:%.o=%) || true + +status: + @modinfo $(obj-m:%.o=%) + @echo loaded: + @lsmod | grep $(obj-m:%.o=%) || echo not loaded + +debug: $(obj-m:%.o=%.ko) + @objdump -dS $< > $(obj-m:%.o=%.S) + +clean: + $(MAKE) -C "$(KDIR)/build" M="$(CURDIR)" clean + @rm -rf -- $(obj-m:%.o=%.S) diff --git a/kernel/bcm2835_spi_display.c b/kernel/bcm2835_spi_display.c index 346c33e..9df4c0e 100644 --- a/kernel/bcm2835_spi_display.c +++ b/kernel/bcm2835_spi_display.c @@ -1,473 +1,222 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include // Required for the GPIO functions +#include // Required for the IRQ code +#include // Using kobjects for the sysfs bindings +#include // Using the clock to measure time between button presses +#define DEBOUNCE_TIME 200 ///< The default bounce time -- 200ms -#include "../config.h" -#include "../display.h" #include "../spi.h" -#include "../util.h" -#include "../dma.h" -static inline uint64_t tick(void) -{ - struct timespec start = current_kernel_time(); - return start.tv_sec * 1000000 + start.tv_nsec / 1000; +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kevin Peck"); // Thanks to Derek Molloy! +MODULE_DESCRIPTION("Touch screen interrupt driver"); +MODULE_VERSION("0.1"); + +static bool isRising = 0; ///< Rising edge is the default IRQ property +module_param(isRising, bool, S_IRUGO); ///< Param desc. S_IRUGO can be read/not changed +MODULE_PARM_DESC(isRising, " Rising edge = 1, Falling edge = 0 (default)"); ///< parameter description + +static unsigned int gpioTouch = GPIO_SPI0_INTR; +module_param(gpioTouch, uint, S_IRUGO); ///< Param desc. S_IRUGO can be read/not changed +MODULE_PARM_DESC(gpioTouch, " GPIO Touch number (default=GPIO_SPI0_INTR)"); ///< parameter description + +static bool isDebug = 0; +module_param(isDebug, bool, S_IRUGO); +MODULE_PARM_DESC(isDebug, " debug = 1, no-debug = 0 (default)"); + +static char gpioName[8] = "gpioXXX"; ///< Null terminated default string -- just in case +static int irqNumber; ///< Used to share the IRQ number within this file +static int numberPresses = 0; ///< For information, store the number of button presses +static bool isDebounce = 1; ///< Use to store the debounce state (on by default) +static struct timespec ts_last, ts_current, ts_diff; ///< timespecs from linux/time.h (has nano precision) + +/// Function prototype for the custom IRQ handler function -- see below for the implementation +static irq_handler_t tftgpio_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs); + +/** @brief A callback function to output the numberPresses variable + * @param kobj represents a kernel object device that appears in the sysfs filesystem + * @param attr the pointer to the kobj_attribute struct + * @param buf the buffer to which to write the number of presses + * @return return the total number of characters written to the buffer (excluding null) + */ +static ssize_t numberPresses_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ + return sprintf(buf, "%d\n", numberPresses); } -// TODO: Super-dirty temp, factor this into kbuild Makefile. -#include "../spi.cpp" -#include "../dma.cpp" - -volatile SPITask *currentTask = 0; -volatile uint8_t *taskNextByte = 0; -volatile uint8_t *taskEndByte = 0; - -#define SPI_BUS_PROC_ENTRY_FILENAME "bcm2835_spi_display_bus" - -typedef struct mmap_info -{ - char *data; -} mmap_info; - -static void p_vm_open(struct vm_area_struct *vma) -{ +/** @brief A callback function to read in the numberPresses variable + * @param kobj represents a kernel object device that appears in the sysfs filesystem + * @param attr the pointer to the kobj_attribute struct + * @param buf the buffer from which to read the number of presses (e.g., reset to 0). + * @param count the number characters in the buffer + * @return return should return the total number of characters used from the buffer + */ +static ssize_t numberPresses_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count){ + sscanf(buf, "%du", &numberPresses); + return count; } -static void p_vm_close(struct vm_area_struct *vma) -{ +/** @brief Displays the last time the button was pressed -- manually output the date (no localization) */ +static ssize_t lastTime_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ + return sprintf(buf, "%.2lu:%.2lu:%.2lu:%.9lu \n", (ts_last.tv_sec/3600)%24, + (ts_last.tv_sec/60) % 60, ts_last.tv_sec % 60, ts_last.tv_nsec ); } -static int p_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) -{ - mmap_info *info = (mmap_info *)vma->vm_private_data; - if (info->data) - { - struct page *page = virt_to_page(info->data + vmf->pgoff*PAGE_SIZE); - get_page(page); - vmf->page = page; - } - return 0; +/** @brief Display the time difference in the form secs.nanosecs to 9 places */ +static ssize_t diffTime_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ + return sprintf(buf, "%lu.%.9lu\n", ts_diff.tv_sec, ts_diff.tv_nsec); } -static struct vm_operations_struct vm_ops = -{ - .open = p_vm_open, - .close = p_vm_close, - .fault = p_vm_fault, -}; - -static int p_mmap(struct file *filp, struct vm_area_struct *vma) -{ - vma->vm_ops = &vm_ops; - vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; - vma->vm_private_data = filp->private_data; - p_vm_open(vma); - return 0; +/** @brief Displays if button debouncing is on or off */ +static ssize_t isDebounce_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){ + return sprintf(buf, "%d\n", isDebounce); } -static int p_open(struct inode *inode, struct file *filp) -{ - mmap_info *info = kmalloc(sizeof(mmap_info), GFP_KERNEL); - info->data = (void*)spiTaskMemory; - filp->private_data = info; - return 0; -} - -static int p_release(struct inode *inode, struct file *filp) -{ - mmap_info *info; - info = filp->private_data; - kfree(info); - filp->private_data = NULL; - return 0; -} - -static const struct file_operations fops = -{ - .mmap = p_mmap, - .open = p_open, - .release = p_release, -}; - -#ifdef KERNEL_DRIVE_WITH_IRQ -static irqreturn_t irq_handler(int irq, void* dev_id) -{ -#ifndef KERNEL_MODULE_CLIENT_DRIVES - uint32_t cs = spi->cs; - if (!taskNextByte) - { - if (currentTask) DoneTask((SPITask*)currentTask); - currentTask = GetTask(); - if (!currentTask) - { - spi->cs = (cs & ~BCM2835_SPI0_CS_TA) | BCM2835_SPI0_CS_CLEAR; - return IRQ_HANDLED; - } - - if ((cs & (BCM2835_SPI0_CS_RXF|BCM2835_SPI0_CS_RXR))) (void)spi->fifo; - while (!(spi->cs & BCM2835_SPI0_CS_DONE)) - { - if ((spi->cs & (BCM2835_SPI0_CS_RXF|BCM2835_SPI0_CS_RXR|BCM2835_SPI0_CS_RXD))) - (void)spi->fifo; - } - CLEAR_GPIO(GPIO_TFT_DATA_CONTROL); - spi->fifo = currentTask->cmd; - if (currentTask->size == 0) // Was this a task without data bytes? If so, nothing more to do here, go to sleep to wait for next IRQ event - { - DoneTask((SPITask*)currentTask); - taskNextByte = 0; - currentTask = 0; +/** @brief Stores and sets the debounce state */ +static ssize_t isDebounce_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){ + unsigned int temp; + sscanf(buf, "%du", &temp); // use a temp varable for correct int->bool + gpio_set_debounce(gpioTouch,0); + isDebounce = temp; + if(isDebounce) { gpio_set_debounce(gpioTouch, DEBOUNCE_TIME); + printk(KERN_INFO "TFT Display: Debounce on\n"); } - else - { - taskNextByte = currentTask->data; - taskEndByte = currentTask->data + currentTask->size; + else { gpio_set_debounce(gpioTouch, 0); // set the debounce time to 0 + printk(KERN_INFO "TFT Display: Debounce off\n"); } -#if 0 // Testing overhead of not returning after command byte, but synchronously polling it out.. - while (!(spi->cs & BCM2835_SPI0_CS_DONE)) ; - (void)spi->fifo; -#else - return IRQ_HANDLED; -#endif - } - if (taskNextByte == currentTask->data) - { - SET_GPIO(GPIO_TFT_DATA_CONTROL); - __sync_synchronize(); - } - - // Test code: write and read from FIFO as many bytes as spec says we should be allowed to, without checking CS in between. -// int maxBytesToSend = (cs & BCM2835_SPI0_CS_DONE) ? 16 : 12; -// if ((cs & BCM2835_SPI0_CS_RXF)) (void)spi->fifo; -// if ((cs & BCM2835_SPI0_CS_RXR)) for(int i = 0; i < MIN(maxBytesToSend, taskEndByte-taskNextByte); ++i) { spi->fifo = *taskNextByte++; (void)spi->fifo; } -// else for(int i = 0; i < MIN(maxBytesToSend, taskEndByte-taskNextByte); ++i) { spi->fifo = *taskNextByte++; } - - while(taskNextByte < taskEndByte) - { - uint32_t cs = spi->cs; - if ((cs & (BCM2835_SPI0_CS_RXR | BCM2835_SPI0_CS_RXF))) spi->cs = cs | BCM2835_SPI0_CS_CLEAR_RX; - if ((cs & BCM2835_SPI0_CS_TXD)) spi->fifo = *taskNextByte++; - if ((cs & BCM2835_SPI0_CS_RXD)) (void)spi->fifo; - else break; - } - - if (taskNextByte >= taskEndByte) - { - if ((cs & BCM2835_SPI0_CS_INTR)) spi->cs = (cs & ~BCM2835_SPI0_CS_INTR) | BCM2835_SPI0_CS_INTD; - taskNextByte = 0; - } - else - { - if (!(cs & BCM2835_SPI0_CS_INTR)) spi->cs = (cs | BCM2835_SPI0_CS_INTR) & ~BCM2835_SPI0_CS_INTR; - } -#endif - return IRQ_HANDLED; -} -#endif - -#define req(cnd) if (!(cnd)) { LOG("!!!%s!!!\n", #cnd);} - -uint32_t virt_to_bus_address(volatile void *virtAddress) -{ - return (uint32_t)virt_to_phys((void*)virtAddress) | 0x40000000U; + return count; } -volatile int shuttingDown = 0; -dma_addr_t spiTaskMemoryPhysical = 0; - -#ifdef USE_DMA_TRANSFERS - -void DMATest(void); - -// Debug code to verify memory->memory streaming of DMA, no SPI peripheral interaction (remove this) -void DMATest() -{ - LOG("Testing DMA transfers"); - - dma_addr_t dma_mem_phys = 0; - void *dma_mem = dma_alloc_writecombine(0, SHARED_MEMORY_SIZE, &dma_mem_phys, GFP_KERNEL); - LOG("Allocated DMA memory: mem: %p, phys: %p", dma_mem, (void*)dma_mem_phys); - - spiTaskMemory = (SharedMemory *)dma_mem; - while(!shuttingDown) - { - msleep(100); - static int ctr = 0; - uint32_t base = (ctr++ * 34153) % SPI_QUEUE_SIZE; - uint32_t size = 65; - uint32_t base2 = base + size; - if (base2 + size > SPI_QUEUE_SIZE) continue; - - memset((void*)spiTaskMemory->buffer, 0xCB, SPI_QUEUE_SIZE); - - uint8_t *src = (uint8_t *)(spiTaskMemory->buffer + base); - src = (uint8_t *)((uintptr_t)src); - for(int i = 0; i < size; ++i) - src[i] = i; - - uint8_t *dst = (uint8_t *)(spiTaskMemory->buffer + base2); - dst = (uint8_t *)((uintptr_t)dst); - -#define TO_BUS(ptr) (( ((uint32_t)dma_mem_phys + ((uintptr_t)(ptr) - (uintptr_t)dma_mem))) | 0xC0000000U) - - volatile DMAChannelRegisterFile *dmaCh = dma+dmaTxChannel; -// printk(KERN_INFO "CS: %x, cbAddr: %p, ti: %x, src: %p, dst: %p, len: %u, stride: %u, nextConBk: %p, debug: %x", -// dmaCh->cs, (void*)dmaCh->cbAddr, dmaCh->cb.ti, (void*)dmaCh->cb.src, (void*)dmaCh->cb.dst, dmaCh->cb.len, dmaCh->cb.stride, (void*)dmaCh->cb.next, dmaCh->cb.debug); - - volatile DMAControlBlock *cb = &spiTaskMemory->cb[0].cb; - req(((uintptr_t)cb) % 256 == 0); - cb->ti = BCM2835_DMA_TI_SRC_INC | BCM2835_DMA_TI_DEST_INC; - cb->src = TO_BUS(src); - cb->dst = TO_BUS(dst); - cb->len = size; - cb->stride = 0; - cb->next = 0; - cb->debug = 0; - cb->reserved = 0; -// DumpCS(dmaCh->cs); -// DumpDebug(dmaCh->cb.debug); -// DumpTI(dmaCh->cb.ti); - LOG("Waiting for transfer %d, src:%p(phys:%p) to dst:%p (phys:%p)", ctr, (void*)src, (void*)cb->src, (void*)dst, (void*)cb->dst); - writel(TO_BUS(cb), &dmaCh->cbAddr); - writel(BCM2835_DMA_CS_ACTIVE | BCM2835_DMA_CS_END | BCM2835_DMA_CS_INT | BCM2835_DMA_CS_WAIT_FOR_OUTSTANDING_WRITES | BCM2835_DMA_CS_SET_PRIORITY(0xF) | BCM2835_DMA_CS_SET_PANIC_PRIORITY(0xF), &dmaCh->cs); +/** Use these helper macros to define the name and access levels of the kobj_attributes + * The kobj_attribute has an attribute attr (name and mode), show and store function pointers + * The count variable is associated with the numberPresses variable and it is to be exposed + * with mode 0666 using the numberPresses_show and numberPresses_store functions above + */ +#undef VERIFY_OCTAL_PERMISSIONS +#define VERIFY_OCTAL_PERMISSIONS(perms) (perms) +static struct kobj_attribute count_attr = __ATTR(numberPresses, 0666, numberPresses_show, numberPresses_store); +static struct kobj_attribute debounce_attr = __ATTR(isDebounce, 0666, isDebounce_show, isDebounce_store); + +/** The __ATTR_RO macro defines a read-only attribute. There is no need to identify that the + * function is called _show, but it must be present. __ATTR_WO can be used for a write-only + * attribute but only in Linux 3.11.x on. + */ +static struct kobj_attribute time_attr = __ATTR_RO(lastTime); ///< the last time pressed kobject attr +static struct kobj_attribute diff_attr = __ATTR_RO(diffTime); ///< the difference in time attr + +/** The tft_attrs[] is an array of attributes that is used to create the attribute group below. + * The attr property of the kobj_attribute is used to extract the attribute struct + */ +static struct attribute *tft_attrs[] = { + &count_attr.attr, ///< The number of button presses + &time_attr.attr, ///< Time of the last button press in HH:MM:SS:NNNNNNNNN + &diff_attr.attr, ///< The difference in time between the last two presses + &debounce_attr.attr, ///< Is the debounce state true or false + NULL, +}; - while((readl(&dmaCh->cs) & BCM2835_DMA_CS_ACTIVE) && !shuttingDown) - { - cpu_relax(); - } +/** The attribute group uses the attribute array and a name, which is exposed on sysfs -- in this + * case it is gpio115, which is automatically defined in the tftDisplay_init() function below + * using the custom kernel parameter that can be passed when the module is loaded. + */ +static struct attribute_group attr_group = { + .name = gpioName, ///< The name is generated in tftDisplay_init() + .attrs = tft_attrs, ///< The attributes array defined just above +}; - if (shuttingDown) - { - LOG("Module shutdown"); - spiTaskMemory = 0; - return; +static struct kobject *tft_kobj; + +/** @brief The LKM initialization function + * The static keyword restricts the visibility of the function to within this C file. The __init + * macro means that for a built-in driver (not a LKM) the function is only used at initialization + * time and that it can be discarded and its memory freed up after that point. In this example this + * function sets up the GPIOs and the IRQ + * @return returns 0 if successful + */ +static int __init tftDisplay_init(void){ + int result = 0; + unsigned long IRQflags = IRQF_TRIGGER_FALLING; // The default is a falling-edge interrupt + + printk(KERN_INFO "TFT Disply: Initializing the Touch interrupt\n"); + sprintf(gpioName, "gpio%d", gpioTouch); // Create the gpioXXX name for /sys/tft/gpioXXX + + // create the kobject sysfs entry at /sys/tft + tft_kobj = kobject_create_and_add("tft", kernel_kobj->parent); // kernel_kobj points to /sys/kernel + if(!tft_kobj){ + printk(KERN_ALERT "TFT Display: failed to create kobject mapping\n"); + return -ENOMEM; } - int errors = 0; - for(int i = 0; i < size; ++i) - if (dst[i] != src[i]) - { - errors = true; - break; - } - - if (errors) - { - printk(KERN_INFO "CS: %x, cbAddr: %p, ti: %x, src: %p, dst: %p, len: %u, stride: %u, nextConBk: %p, debug: %x", - dmaCh->cs, (void*)dmaCh->cbAddr, dmaCh->cb.ti, (void*)dmaCh->cb.src, (void*)dmaCh->cb.dst, dmaCh->cb.len, dmaCh->cb.stride, (void*)dmaCh->cb.next, dmaCh->cb.debug); - for(int i = 0; i < size; ++i) - { - printk(KERN_INFO "Result %p %d: %x vs dst %p %x\n", (void*)virt_to_phys(src+i), i, src[i], (void*)virt_to_phys(dst+i), dst[i]); - } - DumpCS(dmaCh->cs); - DumpDebug(dmaCh->cb.debug); - DumpTI(dmaCh->cb.ti); - LOG("Abort"); - break; + // add the attributes to /sys/tft/ -- for example, /sys/tft/gpio115/numberPresses + result = sysfs_create_group(tft_kobj, &attr_group); + if(result) { + printk(KERN_ALERT "TFT Display: failed to create sysfs group\n"); + kobject_put(tft_kobj); // clean up -- remove the kobject sysfs entry + return result; } - } - LOG("DMA transfer test done"); - spiTaskMemory = 0; -} -#endif - -void PumpSPI(void) -{ -#ifdef KERNEL_DRIVE_WITH_IRQ - spi->cs = BCM2835_SPI0_CS_CLEAR | BCM2835_SPI0_CS_TA | BCM2835_SPI0_CS_INTR | BCM2835_SPI0_CS_INTD; // Initialize the Control and Status register to defaults: CS=0 (Chip Select), CPHA=0 (Clock Phase), CPOL=0 (Clock Polarity), CSPOL=0 (Chip Select Polarity), TA=0 (Transfer not active), and reset TX and RX queues. -#else - if (spiTaskMemory->queueTail != spiTaskMemory->queueHead) - { - BEGIN_SPI_COMMUNICATION(); - { - int i = 0; - while(spiTaskMemory->queueTail != spiTaskMemory->queueHead) - { - ++i; - if (i > 500) break; - SPITask *task = GetTask(); - if (task) - { - RunSPITask(task); - DoneTask(task); - } - else - break; - } + getnstimeofday(&ts_last); // set the last time to be the current time + ts_diff = timespec_sub(ts_last, ts_last); // set the initial time difference to be 0 + + // the bool argument prevents the direction from being changed + gpio_request(gpioTouch, "sysfs"); // Set up the gpioTouch + gpio_direction_input(gpioTouch); // Set the button GPIO to be an input + gpio_set_debounce(gpioTouch, DEBOUNCE_TIME); // Debounce the button with a delay of 200ms + gpio_export(gpioTouch, false); // Causes gpio115 to appear in /sys/class/gpio + // the bool argument prevents the direction from being changed + + // Perform a quick test to see that the button is working as expected on LKM load + printk(KERN_INFO "TFT Display: The touch state is currently: %d\n", gpio_get_value(gpioTouch)); + + /// GPIO numbers and IRQ numbers are not the same! This function performs the mapping for us + irqNumber = gpio_to_irq(gpioTouch); + printk(KERN_INFO "TFT Display: The touch is mapped to IRQ: %d\n", irqNumber); + + if(isRising){ // If the kernel parameter isRising=0 is supplied + IRQflags = IRQF_TRIGGER_RISING; // Set the interrupt to be on the falling edge } - END_SPI_COMMUNICATION(); - } -#endif -} - -static struct timer_list my_timer; - void my_timer_callback( unsigned long data ) -{ - if (shuttingDown) return; - - PumpSPI(); - int ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(1) ); - if (ret) printk("Error in mod_timer\n"); + // This next call requests an interrupt line + result = request_irq(irqNumber, // The interrupt number requested + (irq_handler_t) tftgpio_irq_handler, // The pointer to the handler function below + IRQflags, // Use the custom kernel param to set interrupt type + "tft_touch_handler", // Used in /proc/interrupts to identify the owner + NULL); // The *dev_id for shared interrupt lines, NULL is okay + return result; } -static int display_initialization_thread(void *unused) -{ - printk(KERN_INFO "BCM2835 SPI Display driver thread started"); - -#ifndef KERNEL_MODULE_CLIENT_DRIVES - - // Initialize display. TODO: Move to be shared with ili9341.cpp. - QUEUE_SPI_TRANSFER(0xC0/*Power Control 1*/, 0x23/*VRH=4.60V*/); // Set the GVDD level, which is a reference level for the VCOM level and the grayscale voltage level. - QUEUE_SPI_TRANSFER(0xC1/*Power Control 2*/, 0x10/*AVCC=VCIx2,VGH=VCIx7,VGL=-VCIx4*/); // Sets the factor used in the step-up circuits. To reduce power consumption, set a smaller factor. - QUEUE_SPI_TRANSFER(0xC5/*VCOM Control 1*/, 0x3e/*VCOMH=4.250V*/, 0x28/*VCOML=-1.500V*/); // Adjusting VCOM 1 and 2 can control display brightness - QUEUE_SPI_TRANSFER(0xC7/*VCOM Control 2*/, 0x86/*VCOMH=VMH-58,VCOML=VML-58*/); - -#define MADCTL_ROW_COLUMN_EXCHANGE (1<<5) -#define MADCTL_BGR_PIXEL_ORDER (1<<3) -#define MADCTL_ROTATE_180_DEGREES (MADCTL_COLUMN_ADDRESS_ORDER_SWAP | MADCTL_ROW_ADDRESS_ORDER_SWAP) - uint8_t madctl = MADCTL_BGR_PIXEL_ORDER; -#ifdef DISPLAY_OUTPUT_LANDSCAPE - madctl |= MADCTL_ROW_COLUMN_EXCHANGE; -#endif -#ifdef DISPLAY_ROTATE_180_DEGREES - madctl ^= MADCTL_ROTATE_180_DEGREES; -#endif - QUEUE_SPI_TRANSFER(0x36/*MADCTL: Memory Access Control*/, madctl); - QUEUE_SPI_TRANSFER(0x3A/*COLMOD: Pixel Format Set*/, 0x55/*DPI=16bits/pixel,DBI=16bits/pixel*/); - QUEUE_SPI_TRANSFER(0xB1/*Frame Rate Control (In Normal Mode/Full Colors)*/, 0x00/*DIVA=fosc*/, 0x18/*RTNA(Frame Rate)=79Hz*/); - QUEUE_SPI_TRANSFER(0xB6/*Display Function Control*/, 0x08/*PTG=Interval Scan,PT=V63/V0/VCOML/VCOMH*/, 0x82/*REV=1(Normally white),ISC(Scan Cycle)=5 frames*/, 0x27/*LCD Driver Lines=320*/); - QUEUE_SPI_TRANSFER(0x26/*Gamma Set*/, 0x01/*Gamma curve 1 (G2.2)*/); - QUEUE_SPI_TRANSFER(0xE0/*Positive Gamma Correction*/, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00); - QUEUE_SPI_TRANSFER(0xE1/*Negative Gamma Correction*/, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F); - QUEUE_SPI_TRANSFER(0x11/*Sleep Out*/); - - PumpSPI(); - msleep(1000); - QUEUE_SPI_TRANSFER(/*Display ON*/0x29); - -#if 1 - // XXX Debug: Random garbage to verify screen updates working - for(int y = 0; y < DISPLAY_HEIGHT; ++y) - { - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_X, 0, 0, DISPLAY_WIDTH >> 8, DISPLAY_WIDTH & 0xFF); - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_Y, y >> 8, y & 0xFF, DISPLAY_HEIGHT >> 8, DISPLAY_HEIGHT & 0xFF); - SPITask *clearLine = AllocTask(DISPLAY_SCANLINE_SIZE); - clearLine->cmd = DISPLAY_WRITE_PIXELS; - clearLine->size = DISPLAY_SCANLINE_SIZE; - for(int i = 0; i < DISPLAY_SCANLINE_SIZE; ++i) - clearLine->data[i] = tick() * y + i; - CommitTask(clearLine); - } - PumpSPI(); - msleep(1000); -#endif - - // Initial screen clear - for(int y = 0; y < DISPLAY_HEIGHT; ++y) - { - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_X, 0, 0, DISPLAY_WIDTH >> 8, DISPLAY_WIDTH & 0xFF); - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_Y, y >> 8, y & 0xFF, DISPLAY_HEIGHT >> 8, DISPLAY_HEIGHT & 0xFF); - SPITask *clearLine = AllocTask(DISPLAY_SCANLINE_SIZE); - clearLine->cmd = DISPLAY_WRITE_PIXELS; - clearLine->size = DISPLAY_SCANLINE_SIZE; - memset((void*)clearLine->data, 0, DISPLAY_SCANLINE_SIZE); - CommitTask(clearLine); - } - PumpSPI(); - - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_X, 0, 0, DISPLAY_WIDTH >> 8, DISPLAY_WIDTH & 0xFF); - QUEUE_SPI_TRANSFER(DISPLAY_SET_CURSOR_Y, 0, 0, DISPLAY_HEIGHT >> 8, DISPLAY_HEIGHT & 0xFF); - - spi->cs = BCM2835_SPI0_CS_CLEAR | BCM2835_SPI0_CS_TA | BCM2835_SPI0_CS_INTR | BCM2835_SPI0_CS_INTD; -#endif - - PumpSPI(); - - // Expose SPI worker ring bus to user space driver application. - proc_create(SPI_BUS_PROC_ENTRY_FILENAME, 0, NULL, &fops); - -#if 0 - // XXX Debug: - DMATest(); -#endif - - setup_timer(&my_timer, my_timer_callback, 0); - printk("Starting timer to fire in 200ms (%ld)\n", jiffies); - int ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) ); - if (ret) printk("Error in mod_timer\n"); - - return 0; +/** @brief The LKM cleanup function + * Similar to the initialization function, it is static. The __exit macro notifies that if this + * code is used for a built-in driver (not a LKM) that this function is not required. + */ +static void __exit tftDisplay_exit(void){ + printk(KERN_INFO "TFT Display: The touch was pressed %d times\n", numberPresses); + kobject_put(tft_kobj); // clean up -- remove the kobject sysfs entry + free_irq(irqNumber, NULL); // Free the IRQ number, no *dev_id required in this case + gpio_unexport(gpioTouch); // Unexport the Button GPIO + gpio_free(gpioTouch); // Free the Button GPIO + printk(KERN_INFO "TFT Display: Goodbye from the touch driver!\n"); } -static struct task_struct *displayThread = 0; -static uint32_t irqHandlerCookie = 0; -static uint32_t irqRegistered = 0; - -int bcm2835_spi_display_init(void) -{ - InitSPI(); -#ifdef KERNEL_DRIVE_WITH_IRQ - int ret = request_irq(84, irq_handler, IRQF_SHARED, "spi_handler", &irqHandlerCookie); - if (ret != 0) FATAL_ERROR("request_irq failed!"); - irqRegistered = 1; -#endif - - if (!spiTaskMemory) FATAL_ERROR("Shared memory block not initialized!"); - -#ifdef USE_DMA_TRANSFERS - printk(KERN_INFO "DMA TX channel: %d, irq: %d", dmaTxChannel, dmaTxIrq); - printk(KERN_INFO "DMA RX channel: %d, irq: %d", dmaRxChannel, dmaRxIrq); - spiTaskMemory->dmaTxChannel = dmaTxChannel; - spiTaskMemory->dmaRxChannel = dmaRxChannel; -#endif - - spiTaskMemory->sharedMemoryBaseInPhysMemory = (uint32_t)virt_to_phys(spiTaskMemory) | 0x40000000U; - LOG("PhysBase: %p", (void*)spiTaskMemory->sharedMemoryBaseInPhysMemory); - - displayThread = kthread_create(display_initialization_thread, NULL, "display_thread"); - if (displayThread) wake_up_process(displayThread); - - return 0; +/** @brief The GPIO IRQ Handler function + * This function is a custom interrupt handler that is attached to the GPIO above. The same interrupt + * handler cannot be invoked concurrently as the interrupt line is masked out until the function is complete. + * This function is static as it should not be invoked directly from outside of this file. + * @param irq the IRQ number that is associated with the GPIO -- useful for logging. + * @param dev_id the *dev_id that is provided -- can be used to identify which device caused the interrupt + * Not used in this example as NULL is passed. + * @param regs h/w specific register values -- only really ever used for debugging. + * return returns IRQ_HANDLED if successful -- should return IRQ_NONE otherwise. + */ +static irq_handler_t tftgpio_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs){ + getnstimeofday(&ts_current); // Get the current time as ts_current + ts_diff = timespec_sub(ts_current, ts_last); // Determine the time difference between last 2 presses + ts_last = ts_current; // Store the current time as the last time ts_last + if(isDebug) printk(KERN_INFO "TFT Display: The touch state is currently: %d\n", gpio_get_value(gpioTouch)); + numberPresses++; // Global counter, will be outputted when the module is unloaded + return (irq_handler_t) IRQ_HANDLED; // Announce that the IRQ has been handled correctly } -void bcm2835_spi_display_exit(void) -{ - shuttingDown = 1; - msleep(2000); - spi->cs = BCM2835_SPI0_CS_CLEAR; - msleep(200); - DeinitSPI(); - - if (irqRegistered) - { - free_irq(84, &irqHandlerCookie); - irqRegistered = 0; - } - - remove_proc_entry(SPI_BUS_PROC_ENTRY_FILENAME, NULL); - - int ret = del_timer( &my_timer ); - if (ret) printk("The timer is still in use...\n");} - -module_init(bcm2835_spi_display_init); -module_exit(bcm2835_spi_display_exit); +// This next calls are mandatory -- they identify the initialization function +// and the cleanup function (as above). +module_init(tftDisplay_init); +module_exit(tftDisplay_exit); diff --git a/kernel/build_kernel_module.sh b/kernel/build_kernel_module.sh index 98a3804..c022897 100755 --- a/kernel/build_kernel_module.sh +++ b/kernel/build_kernel_module.sh @@ -1,6 +1,5 @@ -sudo ./stop_kernel_module.sh -sudo make -C /lib/modules/$(uname -r)/build M=$(pwd) modules +sudo make #For debugging: generate disassembly output: -#objdump -dS bcm2835_spi_display.ko > bcm2835_spi_display.S +#make debug diff --git a/kernel/gpioirq.c b/kernel/gpioirq.c new file mode 100644 index 0000000..7b983ee --- /dev/null +++ b/kernel/gpioirq.c @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpioirq.h" + +#define CLASS_NAME "GPIO_IRQ" + +#define N_GPIO_HEADER 26 + +#define GPFSEL0 (0x00) +#define GPFSEL1 (0x04) +#define GPFSEL2 (0x08) +#define GPFSEL3 (0x0C) +#define GPFSEL4 (0x10) +#define GPFSEL5 (0x14) + +#define GPREN0 (0X4C) +#define GPFEN0 (0X58) +#define GPHEN0 (0X64) +#define GPLEN0 (0X70) + +#define IRQEN2 (0x14) +#define IRQDS2 (0x20) + +#define GPPUD (0x94) +#define GPPUDCLK0 (0x98) + +// Original credits to this person +// MODULE_AUTHOR("Jose Martins - josemartins90@gmail.com"); +// +MODULE_AUTHOR("Kevin Peck - kevindpeck@gmail.com"); +MODULE_LICENSE("GPL"); + +static const int +gpio_header_pins[N_GPIO_HEADER] = {22}; + +struct gpio_irq { + int pin; + int irq; + struct cdev c_dev; + struct fasync_struct *fa; + dev_t majorminor; + atomic_t atom; + unsigned type; + unsigned pull; + +}; + +static struct gpio_irq *gpio_irq_list; + +static struct class *gpioirq_class = NULL; +static dev_t majorminor; + +static void *gpio_base, *irqctrl_base; + +irqreturn_t myhandler(int irq, void *dev_id){ + + struct gpio_irq *gpir = dev_id; + + printk(KERN_INFO "GPIO interrupt generated by pin %d\n", gpir->pin); + + if(gpir->fa){ + kill_fasync(&(gpir->fa), SIGIO, POLL_IN); + } + + return IRQ_HANDLED; +} + + +/* THIS WAS NOT TESTED */ +/* +static int gpioirq_setpull(struct gpio_irq *gpir, int type){ + + if(type == GPIOIRQ_PULLUP){ + iowrite32(0x02, gpio_base + GPPUD); + } else if (type == GPIOIRQ_PULLUP){ + iowrite32(0x01, gpio_base + GPPUD); + } else if (type == GPIOIRQ_PULLUP) { + iowrite32(0x00, gpio_base + GPPUD); + } else { + return -EINVAL; + } + + udelay(1); + iowrite32(1 << gpir->pin, gpio_base + GPPUDCLK0); + udelay(1); + iowrite32(0, gpio_base + GPPUDCLK0); + + return 0; +} +*/ + +static int gpioirq_settype(struct gpio_irq *gpir, unsigned long type){ + + unsigned long temp = 0; + + if((type & GPIOIRQ_FALLING_EDGE) && !(gpir->type & type)){ + temp = ioread32(gpio_base + GPFEN0); + temp |= (1 << gpir->pin); + iowrite32(temp, gpio_base + GPFEN0); + gpir->type |= GPIOIRQ_FALLING_EDGE; + } else if(!(type & GPIOIRQ_FALLING_EDGE) && (gpir->type & GPIOIRQ_FALLING_EDGE)){ + temp = ioread32(gpio_base + GPFEN0); + temp &= ~(1 << gpir->pin); + iowrite32(temp, gpio_base + GPFEN0); + gpir->type &= ~GPIOIRQ_FALLING_EDGE; + } + + if(type & GPIOIRQ_RISING_EDGE && !(gpir->type & type)){ + temp = ioread32(gpio_base + GPREN0); + temp |= (1 << gpir->pin); + iowrite32(temp, gpio_base + GPREN0); + gpir->type |= GPIOIRQ_RISING_EDGE; + } else if(!(type & GPIOIRQ_RISING_EDGE) && (gpir->type & GPIOIRQ_RISING_EDGE)){ + temp = ioread32(gpio_base + GPREN0); + temp &= ~(1 << gpir->pin); + iowrite32(temp, gpio_base + GPREN0); + gpir->type &= ~GPIOIRQ_RISING_EDGE; + } + + if(type & GPIOIRQ_HIGH && !(gpir->type & type)){ + temp = ioread32(gpio_base + GPHEN0); + temp |= (1 << gpir->pin); + iowrite32(temp, gpio_base + GPHEN0); + gpir->type |= GPIOIRQ_HIGH; + } else if(!(type & GPIOIRQ_HIGH) && (gpir->type & GPIOIRQ_HIGH)){ + temp = ioread32(gpio_base + GPHEN0); + temp &= ~(1 << gpir->pin); + iowrite32(temp, gpio_base + GPHEN0); + gpir->type &= ~GPIOIRQ_HIGH; + } + + if(type & GPIOIRQ_LOW && !(gpir->type & type)){ + temp = ioread32(gpio_base + GPLEN0); + temp |= (1 << gpir->pin); + iowrite32(temp, gpio_base + GPLEN0); + gpir->type |= GPIOIRQ_LOW; + + } else if(!(type & GPIOIRQ_LOW) && (gpir->type & GPIOIRQ_LOW)){ + temp = ioread32(gpio_base + GPLEN0); + temp &= ~(1 << gpir->pin); + iowrite32(temp, gpio_base + GPLEN0); + gpir->type &= ~GPIOIRQ_LOW; + } + + + return 0; +} + +static int my_open(struct inode *node, struct file *file){ + + unsigned long temp = 0; + int offset; + struct gpio_irq *gpir = container_of(node->i_cdev, struct gpio_irq, c_dev); + + if(!atomic_dec_and_test(&(gpir->atom))){ + atomic_inc(&(gpir->atom)); + return -EBUSY; + } + + file->private_data = gpir; + gpir->irq = gpio_to_irq(gpir->pin); + + if(request_irq(gpir->irq, myhandler, 0, "GPIO_IRQ", gpir)){ + printk(KERN_INFO "Failed Requesting Interrupt Line..."); + return -1; + } + + if(gpir->pin >= 0 && gpir->pin <= 9){ + offset = GPFSEL0; + } else if (gpir->pin >= 10 && gpir->pin <= 19){ + offset = GPFSEL1; + } else { + offset = GPFSEL2; + } + + /* Configure pin as input */ + temp = ioread32(gpio_base + offset); + temp &= (7 << ((gpir->pin % 10)) * 3); + iowrite32(temp, gpio_base + offset); + + /* Interrupt type is set falling edge by default */ + gpir->type = GPIOIRQ_NONE; + gpioirq_settype(gpir, GPIOIRQ_FALLING_EDGE); + + return 0; +} + +static int my_release(struct inode *node, struct file *file){ + + struct gpio_irq *gpir = file->private_data; + + //Disbales Interrupt for pin + gpioirq_settype(gpir, GPIOIRQ_NONE); + + fasync_helper(-1, file, 0, &(gpir->fa)); + free_irq(gpir->irq, gpir); + atomic_inc(&(gpir->atom)); + + return 0; +} + +static int my_fasync(int fd, struct file *filp, int mode){ + + int ret; + struct gpio_irq *gpir = filp->private_data; + + if((ret = fasync_helper(fd, filp, mode, &(gpir->fa))) < 0){ + printk(KERN_INFO "Fasync Failed: %d\n", ret); + return ret; + } + + return 0; +} + + + +static long my_ioctl(struct file *fp, unsigned int cmd, unsigned long arg){ + + struct gpio_irq *gpir = fp->private_data; + + switch(cmd){ + + case GPIOIRQ_IOC_SETTYPE : + return gpioirq_settype(gpir, arg); + break; + case GPIOIRQ_IOC_SETPULL: + //return gpioirq_setpull(gpir, arg); + break; + default: + return -ENOTTY; + } + + return 0; +} + +static struct file_operations fops = { + + .owner = THIS_MODULE, + .open = my_open, + .fasync = my_fasync, + .release = my_release, + .unlocked_ioctl = my_ioctl + +}; + + +static __init int my_init(void){ + + unsigned long temp = 0; + int i, j, ret = 0; + dev_t tempmajorminor; + char device_name[11]; + + if((ret = alloc_chrdev_region(&majorminor, 0, N_GPIO_HEADER, CLASS_NAME)) < 0){ + printk(KERN_INFO "Failed allocating major...\n"); + return ret; + } + + if(IS_ERR(gpioirq_class = class_create(THIS_MODULE, CLASS_NAME))){ + printk(KERN_INFO "Failed creating class..\n"); + ret = PTR_ERR(gpioirq_class); + goto classcreat_err; + } + + gpio_irq_list = kmalloc(N_GPIO_HEADER * sizeof(struct gpio_irq), GFP_KERNEL); + if(!gpio_irq_list){ + ret = ENOMEM; + goto classcreat_err; + } + memset(gpio_irq_list, 0, N_GPIO_HEADER * sizeof(struct gpio_irq)); + + + for(i = 0; i < N_GPIO_HEADER; i++){ + + gpio_irq_list[i].pin = gpio_header_pins[i]; + gpio_irq_list[i].fa = NULL; + + snprintf(device_name, 11, "gpio_irq%d", gpio_header_pins[i]); + + tempmajorminor = MKDEV(MAJOR(majorminor), i ); + gpio_irq_list[i].majorminor = tempmajorminor; + + if(IS_ERR(device_create(gpioirq_class, NULL, tempmajorminor, NULL, device_name))){ + printk(KERN_INFO "Failed creating device...\n"); + ret = PTR_ERR(gpioirq_class); + goto devicecreat_err; + } + + cdev_init(&(gpio_irq_list[i].c_dev), &fops); + gpio_irq_list[i].c_dev.owner = THIS_MODULE; + gpio_irq_list[i].c_dev.ops = &fops; + + if((ret = (cdev_add(&(gpio_irq_list[i].c_dev), tempmajorminor, 1))) < 0){ + printk(KERN_INFO "Failed register device...\n"); + goto devadd_err; + } + + atomic_set(&(gpio_irq_list[i].atom), 1); + + } + + if((gpio_base = ioremap(GPIO_BASE, 0x60)) == NULL){ + printk(KERN_INFO "Failed mapping gpio registers...\n"); + ret = -1; + goto devicecreat_err; + } + if((irqctrl_base = ioremap(ARMCTRL_IC_BASE, 0x28)) == NULL){ + printk(KERN_INFO "Failed mapping interrupt control registers...\n"); + ret = -1; + goto ioremap_err; + } + + /* Enables interrupts for gpio 0-31*/ + temp = ioread32(irqctrl_base + IRQEN2); + temp = (1 << 17); + iowrite32(temp, irqctrl_base + IRQEN2); + + printk(KERN_INFO "GPIO_IRQ Module initialized.\n"); + return 0; + +ioremap_err: + iounmap(gpio_base); +devadd_err: + device_destroy(gpioirq_class, gpio_irq_list[i].majorminor); +devicecreat_err: + for(j = 0; j < i; j++){ + cdev_del(&(gpio_irq_list[j].c_dev)); + device_destroy(gpioirq_class, gpio_irq_list[j].majorminor); + } + class_destroy(gpioirq_class); +classcreat_err: + unregister_chrdev_region(majorminor, 1); + return ret; +} + +static __exit void my_exit(void){ + + int i; + unsigned long temp = 0; + + for(i = 0; i < N_GPIO_HEADER; i++){ + cdev_del(&(gpio_irq_list[i].c_dev)); + device_destroy(gpioirq_class, gpio_irq_list[i].majorminor); + } + + class_destroy(gpioirq_class); + unregister_chrdev_region(majorminor, 1); + + /* Disables interrupts for gpio 0-31*/ + temp = ioread32(irqctrl_base + IRQDS2); + temp = (1 << 17); + iowrite32(temp, irqctrl_base + IRQDS2); + + iounmap(gpio_base); + iounmap(irqctrl_base); + + + printk(KERN_INFO "GPIO_IRQ Module exits...\n"); + +} + +module_init(my_init); +module_exit(my_exit); diff --git a/kernel/gpioirq.h b/kernel/gpioirq.h new file mode 100644 index 0000000..b52ac2b --- /dev/null +++ b/kernel/gpioirq.h @@ -0,0 +1,23 @@ +#ifndef __GPIO_IRQ_H__ +#define __GPIO_IRQ_H__ + +#include +#include + +#define GPIOIRQ_MAGIC ('x') + +#define GPIOIRQ_FALLING_EDGE (0x08) +#define GPIOIRQ_RISING_EDGE (0x04) +#define GPIOIRQ_HIGH (0x02) +#define GPIOIRQ_LOW (0x01) +#define GPIOIRQ_NONE (0x00) + +#define GPIOIRQ_PULLUP 2 +#define GPIOIRQ_PULLDOWN 1 +#define GPIOIRQ_NOPULL 0 + +#define GPIOIRQ_IOC_SETTYPE _IOW(GPIOIRQ_MAGIC, 1, __u8) +#define GPIOIRQ_IOC_SETPULL _IOW(GPIOIRQ_MAGIC, 2, __u8) + + +#endif diff --git a/kernel/start_kernel_module.sh b/kernel/start_kernel_module.sh index 617337e..2425586 100755 --- a/kernel/start_kernel_module.sh +++ b/kernel/start_kernel_module.sh @@ -1,3 +1,3 @@ echo Starting kernel module bcm2835_spi_display.ko -sudo insmod bcm2835_spi_display.ko +sudo make install start diff --git a/kernel/stop_kernel_module.sh b/kernel/stop_kernel_module.sh index 417d30a..6bbef24 100755 --- a/kernel/stop_kernel_module.sh +++ b/kernel/stop_kernel_module.sh @@ -1,10 +1,3 @@ -# Kill user space driver program first if it happens to be running, because otherwise shutting down the kernel -# module would crash the system if the userland program was still accessing it. -echo Killing existing instances of user space driver program fbcp-ili9341 -sudo pkill fbcp-ili9341 -sudo pkill fbcp-ili9341-stable - -# Now safe to tear down the module echo Stopping kernel module bcm2835_spi_display.ko -sudo rmmod bcm2835_spi_display.ko +sudo make stop diff --git a/keyboard.cpp b/keyboard.cpp index 4b83ac4..77d5582 100644 --- a/keyboard.cpp +++ b/keyboard.cpp @@ -18,7 +18,7 @@ void OpenKeyboard() { #ifdef READ_KEYBOARD_ENABLED key_fd = open(KEYBOARD_INPUT_FILE, O_RDONLY|O_NONBLOCK); - if (key_fd < 0) printf("Warning: cannot open keyboard input file " KEYBOARD_INPUT_FILE "! Try double checking that it exists, or reconfigure it in keyboard.cpp, or remove line '#define BACKLIGHT_CONTROL_FROM_KEYBOARD' in config.h if you do not want keyboard activity to factor into backlight control.\n"); + if (key_fd < 0) printf("Warning: cannot open keyboard input file " KEYBOARD_INPUT_FILE "! Try double checking that it exists, or reconfigure it in keyboard.cpp.\n"); #endif } diff --git a/mpi3501.cpp b/mpi3501.cpp index 67178a1..0e17cd6 100644 --- a/mpi3501.cpp +++ b/mpi3501.cpp @@ -1,135 +1,209 @@ -#include "config.h" - -#ifdef MPI3501 - -#include "spi.h" - -#include -#include - -void ChipSelectHigh() -{ - WAIT_SPI_FINISHED(); - CLEAR_GPIO(GPIO_SPI0_CE0); // Enable Touch - SET_GPIO(GPIO_SPI0_CE0); // Disable Touch - __sync_synchronize(); - SET_GPIO(GPIO_SPI0_CE1); // Disable Display - CLEAR_GPIO(GPIO_SPI0_CE1); // Enable Display - __sync_synchronize(); -} - -void InitKeDeiV63() -{ - // If a Reset pin is defined, toggle it briefly high->low->high to enable the device. Some devices do not have a reset pin, in which case compile with GPIO_TFT_RESET_PIN left undefined. -#if defined(GPIO_TFT_RESET_PIN) && GPIO_TFT_RESET_PIN >= 0 - printf("Resetting display at reset GPIO pin %d\n", GPIO_TFT_RESET_PIN); - SET_GPIO_MODE(GPIO_TFT_RESET_PIN, 1); - SET_GPIO(GPIO_TFT_RESET_PIN); - usleep(120 * 1000); - CLEAR_GPIO(GPIO_TFT_RESET_PIN); - usleep(120 * 1000); - SET_GPIO(GPIO_TFT_RESET_PIN); - usleep(120 * 1000); -#endif - - // For sanity, start with both Chip selects high to ensure that the display will see a high->low enable transition when we start. - SET_GPIO(GPIO_SPI0_CE0); // Disable Touch - SET_GPIO(GPIO_SPI0_CE1); // Disable Display - usleep(1000); - - // Do the initialization with a very low SPI bus speed, so that it will succeed even if the bus speed chosen by the user is too high. - spi->clk = 34; - __sync_synchronize(); - - BEGIN_SPI_COMMUNICATION(); - { - CLEAR_GPIO(GPIO_SPI0_CE0); // Enable Touch - CLEAR_GPIO(GPIO_SPI0_CE1); // Enable Display - - BEGIN_SPI_COMMUNICATION(); - - usleep(25*1000); - - SET_GPIO(GPIO_SPI0_CE0); // Disable Touch - usleep(25*1000); - - SPI_TRANSFER(0x00000000); // This command seems to be Reset - usleep(120*1000); - - SPI_TRANSFER(0x00000100); - usleep(50*1000); - SPI_TRANSFER(0x00001100); - usleep(60*1000); - - SPI_TRANSFER(0xB9001100, 0x00, 0xFF, 0x00, 0x83, 0x00, 0x57); - usleep(5*1000); - - SPI_TRANSFER(0xB6001100, 0x00, 0x2C); - SPI_TRANSFER(0x11001100/*Sleep Out*/); - usleep(150*1000); - - SPI_TRANSFER(0x3A001100/*Interface Pixel Format*/, 0x00, 0x55); - SPI_TRANSFER(0xB0001100, 0x00, 0x68); - SPI_TRANSFER(0xCC001100, 0x00, 0x09); - SPI_TRANSFER(0xB3001100, 0x00, 0x43, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06); - SPI_TRANSFER(0xB1001100, 0x00, 0x00, 0x00, 0x15, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x83, 0x00, 0x44); - SPI_TRANSFER(0xC0001100, 0x00, 0x24, 0x00, 0x24, 0x00, 0x01, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x08); - SPI_TRANSFER(0xB4001100, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x2A, 0x00, 0x0D, 0x00, 0x4F); - SPI_TRANSFER(0xE0001100, 0x00, 0x02, 0x00, 0x08, 0x00, 0x11, 0x00, 0x23, 0x00, 0x2C, 0x00, 0x40, 0x00, 0x4A, 0x00, 0x52, 0x00, 0x48, 0x00, 0x41, 0x00, 0x3C, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x28, 0x00, 0x27, 0x00, 0x1B, 0x00, 0x02, 0x00, 0x08, 0x00, 0x11, 0x00, 0x23, 0x00, 0x2C, 0x00, 0x40, 0x00, 0x4A, 0x00, 0x52, 0x00, 0x48, 0x00, 0x41, 0x00, 0x3C, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x28, 0x00, 0x27, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01); - -#define MADCTL_BGR_PIXEL_ORDER (1<<3) -#define MADCTL_ROW_COLUMN_EXCHANGE (1<<5) -#define MADCTL_COLUMN_ADDRESS_ORDER_SWAP (1<<6) -#define MADCTL_ROW_ADDRESS_ORDER_SWAP (1<<7) -#define MADCTL_ROTATE_180_DEGREES (MADCTL_COLUMN_ADDRESS_ORDER_SWAP | MADCTL_ROW_ADDRESS_ORDER_SWAP) - - uint8_t madctl = 0; -#ifndef DISPLAY_SWAP_BGR - madctl |= MADCTL_BGR_PIXEL_ORDER; -#endif -#if defined(DISPLAY_FLIP_ORIENTATION_IN_HARDWARE) - madctl |= MADCTL_ROW_COLUMN_EXCHANGE; -#endif -#ifdef DISPLAY_ROTATE_180_DEGREES - madctl ^= MADCTL_ROTATE_180_DEGREES; -#endif - SPI_TRANSFER(0x36001100/*MADCTL: Memory Access Control*/, 0x00, madctl); - - SPI_TRANSFER(0x29001100/*Display ON*/); - - usleep(200*1000); - - ClearScreen(); - } -#ifndef USE_DMA_TRANSFERS // For DMA transfers, keep SPI CS & TA active. - END_SPI_COMMUNICATION(); -#endif - - // And speed up to the desired operation speed finally after init is done. - usleep(10 * 1000); // Delay a bit before restoring CLK, or otherwise this has been observed to cause the display not init if done back to back after the clear operation above. - spi->clk = SPI_BUS_CLOCK_DIVISOR; -} - -void TurnBacklightOff() -{ -} - -void TurnBacklightOn() -{ -} - -void TurnDisplayOff() -{ -} - -void TurnDisplayOn() -{ -} - -void DeinitSPIDisplay() -{ - ClearScreen(); - TurnDisplayOff(); -} - -#endif +#include "config.h" + +#ifdef MPI3501 + +#include "spi_user.h" +#include "XPT2046.h" + +#include +#include +#include +#include + +XPT2046 touch; +static int counter = 0; +char buffer[20]; +short bufLen = 0; +#define LOOP_INTERVAL 500 +static int loop = 0; + +bool activeTouchscreen() { + return (touch.ticksSinceLastTouch() < TURN_DISPLAY_OFF_AFTER_USECS_OF_INACTIVITY); +} + +void ReloadCalibration(int signal) { + touch.initCalibration(); +} + +void ChipSelectHigh() +{ + WAIT_SPI_FINISHED(); + SET_GPIO(GPIO_SPI0_CE1); // Disable Display + CLEAR_GPIO(GPIO_SPI0_CE0); // Enable Touch + __sync_synchronize(); + if(loop++ % LOOP_INTERVAL == 0) { + touch.read_touchscreen(true); + __sync_synchronize(); + } + if(hasInterrupt()) { + touch.read_touchscreen(false); + __sync_synchronize(); + } + SET_GPIO(GPIO_SPI0_CE0); // Disable Touch + CLEAR_GPIO(GPIO_SPI0_CE1); // Enable Display + __sync_synchronize(); +} + +void InitKeDeiV63() +{ + // output device + touch = XPT2046(); + + //Register for system signal + signal(SIGUSR1, ReloadCalibration); + + + touch.setRotation(0); +#ifdef DISPLAY_ROTATE_180_DEGREES + touch.setRotation(1); +#endif + + // If a Reset pin is defined, toggle it briefly high->low->high to enable the device. Some devices do not have a reset pin, in which case compile with GPIO_TFT_RESET_PIN left undefined. +#if defined(GPIO_TFT_RESET_PIN) && GPIO_TFT_RESET_PIN >= 0 + printf("Resetting display at reset GPIO pin %d\n", GPIO_TFT_RESET_PIN); + SET_GPIO_MODE(GPIO_TFT_RESET_PIN, 1); + SET_GPIO(GPIO_TFT_RESET_PIN); + usleep(120 * 1000); + CLEAR_GPIO(GPIO_TFT_RESET_PIN); + usleep(120 * 1000); + SET_GPIO(GPIO_TFT_RESET_PIN); + usleep(120 * 1000); +#endif + + // For sanity, start with both Chip selects high to ensure that the display will see a high->low enable transition when we start. + SET_GPIO(GPIO_SPI0_CE0); // Disable Touch + SET_GPIO(GPIO_SPI0_CE1); // Disable Display + usleep(1000); + + // Do the initialization with a very low SPI bus speed, so that it will succeed even if the bus speed chosen by the user is too high. + spi->clk = 34; + __sync_synchronize(); + + BEGIN_SPI_COMMUNICATION(); + { + CLEAR_GPIO(GPIO_SPI0_CE0); // Enable Touch + CLEAR_GPIO(GPIO_SPI0_CE1); // Enable Display + + BEGIN_SPI_COMMUNICATION(); + + usleep(25*1000); + + SET_GPIO(GPIO_SPI0_CE0); // Disable Touch + usleep(25*1000); + + SPI_TRANSFER(DISPLAY_NO_OPERATION); // Reset + usleep(10*1000); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + usleep(10*1000); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + SPI_TRANSFER(DISPLAY_GETSPIREAD); + usleep(15*1000); + SPI_TRANSFER(DISPLAY_SLPOUT); + usleep(150*1000); + + SPI_TRANSFER(DISPLAY_SETOSC, 0x00, 0x00); + SPI_TRANSFER(DISPLAY_SETRGB, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + SPI_TRANSFER(DISPLAY_SETEXTC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0f); + SPI_TRANSFER(DISPLAY_SETSTBA, 0x00, 0x13, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x02 + , 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x43); + SPI_TRANSFER(DISPLAY_SETDGC, 0x00, 0x08, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x08); + SPI_TRANSFER(DISPLAY_SETDDB, 0x00, 0x11, 0x00, 0x07, 0x00, 0x03, 0x00, 0x04); + SPI_TRANSFER(0xC6001100, 0x00, 0x00); // ? + SPI_TRANSFER(0xC8001100, 0x00, 0x03, 0x00, 0x03, 0x00, 0x13, 0x00, 0x5C + , 0x00, 0x03, 0x00, 0x07, 0x00, 0x14, 0x00, 0x08 + , 0x00, 0x00, 0x00, 0x21, 0x00, 0x08, 0x00, 0x14 + , 0x00, 0x07, 0x00, 0x53, 0x00, 0x0C, 0x00, 0x13 + , 0x00, 0x03, 0x00, 0x03, 0x00, 0x21, 0x00, 0x00); // ? + SPI_TRANSFER(DISPLAY_TEON, 0x00, 0x00); + +#define MADCTL_BGR_PIXEL_ORDER (1<<3) +#define MADCTL_LINE_ADDRESS_ORDER_SWAP (1<<4) +#define MADCTL_ROW_COLUMN_EXCHANGE (1<<5) +#define MADCTL_COLUMN_ADDRESS_ORDER_SWAP (1<<6) +#define MADCTL_ROW_ADDRESS_ORDER_SWAP (1<<7) +#define MADCTL_ROTATE_180_DEGREES ( MADCTL_ROW_ADDRESS_ORDER_SWAP ) + + uint8_t madctl = 0; +#ifndef DISPLAY_SWAP_BGR + madctl |= MADCTL_BGR_PIXEL_ORDER; +#endif +#if defined(DISPLAY_FLIP_ORIENTATION_IN_HARDWARE) + madctl |= MADCTL_ROW_COLUMN_EXCHANGE; +#endif +#ifdef DISPLAY_ROTATE_180_DEGREES + madctl ^= MADCTL_ROTATE_180_DEGREES; +#endif + SPI_TRANSFER(DISPLAY_MADCTL, 0x00, madctl); + + SPI_TRANSFER(DSPLAY_COLMOD, 0x00, 0x55); + SPI_TRANSFER(DISPLAY_WRCABC,0x00,0x00); // 00 - default, 01 - UI, 02 - still pic, 03 - video + //SPI_TRANSFER(DISPLAY_IDMON); // Increases brightness+contrast + //SPI_TRANSFER(DISPLAY_IDMOFF); // Darker but more accurate colour + SPI_TRANSFER(DISPLAY_TESL, 0x00, 0x00, 0x00, 0x01); + + SPI_TRANSFER(DISPLAY_GETICID, 0x00, 0x07, 0x00, 0x07, 0x00, 0x1D, 0x00, 0x03); // ? + SPI_TRANSFER(0xD1001100, 0x00, 0x03, 0x00, 0x30, 0x00, 0x10); // ? + SPI_TRANSFER(0xD2001100, 0x00, 0x03, 0x00, 0x14, 0x00, 0x04); // ? + SPI_TRANSFER(DISPLAY_ON); + + usleep(30*1000); + + SPI_TRANSFER(0xB4001100, 0x00, 0x00); // ? + + usleep(10*1000); + + ClearScreen(); + } +#ifndef USE_DMA_TRANSFERS // For DMA transfers, keep SPI CS & TA active. + END_SPI_COMMUNICATION(); +#endif + + // And speed up to the desired operation speed finally after init is done. + usleep(10 * 1000); // Delay a bit before restoring CLK, or otherwise this has been observed to cause the display not init if done back to back after the clear operation above. + spi->clk = SPI_BUS_CLOCK_DIVISOR; +} + +void TurnBacklightOff() +{ +} + +void TurnBacklightOn() +{ +} + +void TurnDisplayOff() +{ + // These settings appear to to work as per data sheet + SPI_TRANSFER(DISPLAY_WRCTRLD,0x00,0x10); // 5bit-Backlight control is DISPLAY_WRDISBV, 3bit-Display dimming off, 2bit-Backliht Off + SPI_TRANSFER(DISPLAY_WRDISBV,0x00,0x00); + + // This does turn display off but you end up with white screen as backlight remains on + +// SPI_TRANSFER(DISPLAY_OFF); // Works but whith backlight on, it goes white -- a good 'light' +// SPI_TRANSFER(DISPLAY_SLPIN); +// usleep(120*1000); +} + +void TurnDisplayOn() +{ + // These settings appear to to work as per data sheet + SPI_TRANSFER(DISPLAY_WRCTRLD,0x00,0x1C); // 5bit-Backlight control is DISPLAY_WRDISBV, 3bit-Display dimming on, 2bit-Backliht on + SPI_TRANSFER(DISPLAY_WRDISBV,0x00,0xFF); + + // This does turn on display, but no need if you're not turning it off (TurnDisplayOff()) +// SPI_TRANSFER(DISPLAY_SLPOUT); +// usleep(120*1000); +// SPI_TRANSFER(DISPLAY_ON); +} + +void DeinitSPIDisplay() +{ + ClearScreen(); + TurnDisplayOff(); +} + + +#endif diff --git a/mpi3501.h b/mpi3501.h index 5a67d7d..7b2010f 100644 --- a/mpi3501.h +++ b/mpi3501.h @@ -5,9 +5,31 @@ #ifdef MPI3501 // Data specific to the KeDei v6.3 display +#define DISPLAY_NO_OPERATION 0x00001100 +#define DISPLAY_SLPIN 0x10001100 +#define DISPLAY_SLPOUT 0x11001100 +#define DISPLAY_ON 0x29001100 +#define DISPLAY_OFF 0x28001100 #define DISPLAY_SET_CURSOR_X 0x2A001100 #define DISPLAY_SET_CURSOR_Y 0x2B001100 #define DISPLAY_WRITE_PIXELS 0x2C001100 +#define DISPLAY_SETOSC 0xB0001100 +#define DISPLAY_SETRGB 0xB3001100 +#define DISPLAY_SETEXTC 0xB9001100 +#define DISPLAY_SETSTBA 0xC0001100 +#define DISPLAY_SETDGC 0xC1001100 +#define DISPLAY_SETDDB 0xC4001100 +#define DISPLAY_TEON 0x35001100 +#define DISPLAY_MADCTL 0x36001100 +#define DISPLAY_IDMOFF 0x38001100 +#define DISPLAY_IDMON 0x39001100 +#define DSPLAY_COLMOD 0x3A001100 +#define DISPLAY_TESL 0x44001100 +#define DISPLAY_WRDISBV 0x51001100 +#define DISPLAY_WRCTRLD 0x53001100 +#define DISPLAY_WRCABC 0x55001100 +#define DISPLAY_GETICID 0xD0001100 +#define DISPLAY_GETSPIREAD 0xff001100 #define DISPLAY_NATIVE_WIDTH 320 #define DISPLAY_NATIVE_HEIGHT 480 diff --git a/mz61581.cpp b/mz61581.cpp index d7ae001..88950f7 100644 --- a/mz61581.cpp +++ b/mz61581.cpp @@ -2,7 +2,7 @@ #ifdef MZ61581 -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/spi.cpp b/spi.cpp index 80dbe22..a9cb53c 100644 --- a/spi.cpp +++ b/spi.cpp @@ -5,10 +5,11 @@ #include // mmap, munmap #include // pthread_create #include // bcm_host_get_peripheral_address, bcm_host_get_peripheral_size, bcm_host_get_sdram_address +#include "config.h" #endif #include "config.h" -#include "spi.h" +#include "spi_user.h" #include "util.h" #include "dma.h" #include "mailbox.h" @@ -16,7 +17,7 @@ // Uncomment this to print out all bytes sent to the SPI bus // #define DEBUG_SPI_BUS_WRITES - + #ifdef DEBUG_SPI_BUS_WRITES #define DEBUG_PRINT_WRITTEN_BYTE(byte) do { \ printf("%02X", byte); \ @@ -41,16 +42,34 @@ static uint32_t writeCounter = 0; TOGGLE_CHIP_SELECT_LINE(); \ DEBUG_PRINT_WRITTEN_BYTE(w); \ } while(0) - + int mem_fd = -1; +int intr_fd = -1; +static int numberPress = 0; +static char numberAsString[21] = " "; volatile void *bcm2835 = 0; volatile GPIORegisterFile *gpio = 0; volatile SPIRegisterFile *spi = 0; - + // Points to the system timer register. N.B. spec sheet says this is two low and high parts, in an 32-bit aligned (but not 64-bit aligned) address. Profiling shows // that Pi 3 Model B does allow reading this as a u64 load, and even when unaligned, it is around 30% faster to do so compared to loading in parts "lo | (hi << 32)". volatile uint64_t *systemTimerRegister = 0; +bool hasInterrupt() { + int lastNumberPress = numberPress; + + if(intr_fd<0) return false; + if ((read(intr_fd, numberAsString, sizeof(numberAsString))) < 0) { + if (errno != EWOULDBLOCK) { + perror("read/intr_fd"); + } + } else { + lseek(intr_fd,0,SEEK_SET); + numberPress = atoi(numberAsString); + } + return (numberPress != lastNumberPress); +} + void DumpSPICS(uint32_t reg) { PRINT_FLAG(BCM2835_SPI0_CS_CS); @@ -259,6 +278,14 @@ void WaitForPolledSPITransferToFinish() if ((cs & BCM2835_SPI0_CS_RXD)) spi->cs = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; } + +void sendNoOpCommand() { + // Send a no-operation command to display (side effect is Touch display is polled) + SPITask *task = AllocTask(0); + task->cmd = DISPLAY_NO_OPERATION; + CommitTask(task); + IN_SINGLE_THREADED_MODE_RUN_TASK(); +} #ifdef ALL_TASKS_SHOULD_DMA @@ -274,14 +301,13 @@ void RunSPITask(SPITask *task) uint8_t *tEnd = task->PayloadEnd(); const uint32_t payloadSize = tEnd - tStart; uint8_t *tPrefillEnd = tStart + MIN(15, payloadSize); - #define TASK_SIZE_TO_USE_DMA 4 // Do a DMA transfer if this task is suitable in size for DMA to handle if (payloadSize >= TASK_SIZE_TO_USE_DMA && (task->cmd == DISPLAY_WRITE_PIXELS || task->cmd == DISPLAY_SET_CURSOR_X || task->cmd == DISPLAY_SET_CURSOR_Y)) { if (previousTaskWasSPI) WaitForPolledSPITransferToFinish(); -// printf("DMA cmd=0x%x, data=%d bytes\n", task->cmd, task->PayloadSize()); + //printf("DMA cmd=0x%x, data=%d bytes\n", task->cmd, task->PayloadSize()); SPIDMATransfer(task); previousTaskWasSPI = false; } @@ -297,7 +323,7 @@ void RunSPITask(SPITask *task) else WaitForPolledSPITransferToFinish(); -// printf("SPI cmd=0x%x, data=%d bytes\n", task->cmd, task->PayloadSize()); + //printf("SPI cmd=0x%x, data=%d bytes\n", task->cmd, task->PayloadSize()); // Send the command word if display is 4-wire (3-wire displays can omit this, commands are interleaved in the data payload stream above) #ifndef SPI_3WIRE_PROTOCOL @@ -336,7 +362,7 @@ void RunSPITask(SPITask *task) #else void RunSPITask(SPITask *task) -{ +{ WaitForPolledSPITransferToFinish(); // The Adafruit 1.65" 240x240 ST7789 based display is unique compared to others that it does want to see the Chip Select line go @@ -458,7 +484,7 @@ void ExecuteSPITasks() #endif } -#if !defined(KERNEL_MODULE) && defined(USE_SPI_THREAD) +#if defined(USE_SPI_THREAD) pthread_t spiThread; // A worker thread that keeps the SPI bus filled at all times @@ -471,7 +497,7 @@ void *spi_thread(void *unused) { if (spiTaskMemory->queueTail != spiTaskMemory->queueHead) { - ExecuteSPITasks(); + ExecuteSPITasks(); } else { @@ -480,7 +506,9 @@ void *spi_thread(void *unused) spiThreadSleepStartTime = t0; __atomic_store_n(&spiThreadSleeping, 1, __ATOMIC_RELAXED); #endif - if (programRunning) syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAIT, spiTaskMemory->queueHead, 0, 0, 0); // Start sleeping until we get new tasks + if (programRunning) { + syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAIT, spiTaskMemory->queueHead, 0, 0, 0); // Start sleeping until we get new tasks + } #ifdef STATISTICS __atomic_store_n(&spiThreadSleeping, 0, __ATOMIC_RELAXED); uint64_t t1 = tick(); @@ -491,22 +519,11 @@ void *spi_thread(void *unused) pthread_exit(0); } #endif + + int InitSPI() { -#ifdef KERNEL_MODULE - -#define BCM2835_PERI_BASE 0x3F000000 -#define BCM2835_GPIO_BASE 0x200000 -#define BCM2835_SPI0_BASE 0x204000 - printk("ioremapping %p\n", (void*)(BCM2835_PERI_BASE+BCM2835_GPIO_BASE)); - void *bcm2835 = ioremap(BCM2835_PERI_BASE+BCM2835_GPIO_BASE, 32768); - printk("Got bcm address %p\n", bcm2835); - if (!bcm2835) FATAL_ERROR("Failed to map BCM2835 address!"); - spi = (volatile SPIRegisterFile*)((uintptr_t)bcm2835 + BCM2835_SPI0_BASE - BCM2835_GPIO_BASE); - gpio = (volatile GPIORegisterFile*)((uintptr_t)bcm2835); - -#else // Userland version // Memory map GPIO and SPI peripherals for direct access mem_fd = open("/dev/mem", O_RDWR|O_SYNC); if (mem_fd < 0) FATAL_ERROR("can't open /dev/mem (run as sudo)"); @@ -517,8 +534,11 @@ int InitSPI() gpio = (volatile GPIORegisterFile*)((uintptr_t)bcm2835 + BCM2835_GPIO_BASE); systemTimerRegister = (volatile uint64_t*)((uintptr_t)bcm2835 + BCM2835_TIMER_BASE + 0x04); // Generates an unaligned 64-bit pointer, but seems to be fine. // TODO: On graceful shutdown, (ctrl-c signal?) close(mem_fd) -#endif + // Touch screen interrupt + intr_fd = open("/sys/tft/gpio25/numberPresses", O_RDONLY|O_NONBLOCK); + //if (intr_fd < 0) FATAL_ERROR("can't open /sys/tft/gpio25/numberPresses (run as sudo)"); + uint32_t currentBcmCoreSpeed = MailboxRet2(0x00030002/*Get Clock Rate*/, 0x4/*CORE*/); uint32_t maxBcmCoreTurboSpeed = MailboxRet2(0x00030004/*Get Max Clock Rate*/, 0x4/*CORE*/); @@ -527,7 +547,6 @@ int InitSPI() printf("BCM core speed: current: %uhz, max turbo: %uhz. SPI CDIV: %d, SPI max frequency: %.0fhz\n", currentBcmCoreSpeed, maxBcmCoreTurboSpeed, SPI_BUS_CLOCK_DIVISOR, (double)maxBcmCoreTurboSpeed / SPI_BUS_CLOCK_DIVISOR); -#if !defined(KERNEL_MODULE_CLIENT) || defined(KERNEL_MODULE_CLIENT_DRIVES) // By default all GPIO pins are in input mode (0x00), initialize them for SPI and GPIO writes #ifdef GPIO_TFT_DATA_CONTROL SET_GPIO_MODE(GPIO_TFT_DATA_CONTROL, 0x01); // Data/Control pin to output (0x01) @@ -558,38 +577,9 @@ int InitSPI() spi->cs = BCM2835_SPI0_CS_CLEAR | DISPLAY_SPI_DRIVE_SETTINGS; // Initialize the Control and Status register to defaults: CS=0 (Chip Select), CPHA=0 (Clock Phase), CPOL=0 (Clock Polarity), CSPOL=0 (Chip Select Polarity), TA=0 (Transfer not active), and reset TX and RX queues. spi->clk = SPI_BUS_CLOCK_DIVISOR; // Clock Divider determines SPI bus speed, resulting speed=256MHz/clk -#endif - - // Initialize SPI thread task buffer memory -#ifdef KERNEL_MODULE_CLIENT - int driverfd = open("/proc/bcm2835_spi_display_bus", O_RDWR|O_SYNC); - if (driverfd < 0) FATAL_ERROR("Could not open SPI ring buffer - kernel driver module not running?"); - spiTaskMemory = (SharedMemory*)mmap(NULL, SHARED_MEMORY_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED/* | MAP_NORESERVE | MAP_POPULATE | MAP_LOCKED*/, driverfd, 0); - close(driverfd); - if (spiTaskMemory == MAP_FAILED) FATAL_ERROR("Could not mmap SPI ring buffer!"); - printf("Got shared memory block %p, ring buffer head %p, ring buffer tail %p, shared memory block phys address: %p\n", (const char *)spiTaskMemory, spiTaskMemory->queueHead, spiTaskMemory->queueTail, spiTaskMemory->sharedMemoryBaseInPhysMemory); -#ifdef USE_DMA_TRANSFERS - printf("DMA TX channel: %d, DMA RX channel: %d\n", spiTaskMemory->dmaTxChannel, spiTaskMemory->dmaRxChannel); -#endif - -#else - -#ifdef KERNEL_MODULE - spiTaskMemory = (SharedMemory*)kmalloc(SHARED_MEMORY_SIZE, GFP_KERNEL | GFP_DMA); - // TODO: Ideally we would be able to directly perform the DMA from the SPI ring buffer in 'spiTaskMemory'. However - // that pointer is shared to userland, and it is proving troublesome to make it both userland-writable as well as cache-bypassing DMA coherent. - // Therefore these two memory areas are separate for now, and we memcpy() from SPI ring buffer to the following intermediate 'dmaSourceMemory' - // memory area to perform the DMA transfer. Is there a way to avoid this intermediate buffer? That would improve performance a bit. - dmaSourceMemory = (SharedMemory*)dma_alloc_writecombine(0, SHARED_MEMORY_SIZE, &spiTaskMemoryPhysical, GFP_KERNEL); - LOG("Allocated DMA memory: mem: %p, phys: %p", spiTaskMemory, (void*)spiTaskMemoryPhysical); - memset((void*)spiTaskMemory, 0, SHARED_MEMORY_SIZE); -#else spiTaskMemory = (SharedMemory*)Malloc(SHARED_MEMORY_SIZE, "spi.cpp shared task memory"); -#endif - spiTaskMemory->queueHead = spiTaskMemory->queueTail = spiTaskMemory->spiBytesQueued = 0; -#endif #ifdef USE_DMA_TRANSFERS InitDMA(); @@ -598,7 +588,6 @@ int InitSPI() // Enable fast 8 clocks per byte transfer mode, instead of slower 9 clocks per byte. UNLOCK_FAST_8_CLOCKS_SPI(); -#if !defined(KERNEL_MODULE) && (!defined(KERNEL_MODULE_CLIENT) || defined(KERNEL_MODULE_CLIENT_DRIVES)) printf("Initializing display\n"); InitSPIDisplay(); @@ -612,9 +601,6 @@ int InitSPI() // We will be running SPI tasks continuously from the main thread, so keep SPI Transfer Active throughout the lifetime of the driver. BEGIN_SPI_COMMUNICATION(); #endif - -#endif - LOG("InitSPI done"); return 0; } @@ -632,7 +618,6 @@ void DeinitSPI() spi->cs = BCM2835_SPI0_CS_CLEAR | DISPLAY_SPI_DRIVE_SETTINGS; -#ifndef KERNEL_MODULE_CLIENT #ifdef GPIO_TFT_DATA_CONTROL SET_GPIO_MODE(GPIO_TFT_DATA_CONTROL, 0); #endif @@ -641,7 +626,6 @@ void DeinitSPI() SET_GPIO_MODE(GPIO_SPI0_MISO, 0); SET_GPIO_MODE(GPIO_SPI0_MOSI, 0); SET_GPIO_MODE(GPIO_SPI0_CLK, 0); -#endif if (bcm2835) { @@ -654,16 +638,10 @@ void DeinitSPI() close(mem_fd); mem_fd = -1; } - -#ifndef KERNEL_MODULE_CLIENT - -#ifdef KERNEL_MODULE - kfree(spiTaskMemory); - dma_free_writecombine(0, SHARED_MEMORY_SIZE, dmaSourceMemory, spiTaskMemoryPhysical); - spiTaskMemoryPhysical = 0; -#else + if (intr_fd >= 0) { + close(intr_fd); + intr_fd = -1; + } free(spiTaskMemory); -#endif -#endif spiTaskMemory = 0; } diff --git a/spi.h b/spi.h index e88c8d0..52a6719 100644 --- a/spi.h +++ b/spi.h @@ -1,398 +1,69 @@ -#pragma once - -#ifndef KERNEL_MODULE -#include -#include -#endif -#include - -#include "display.h" -#include "tick.h" -#include "dma.h" -#include "display.h" - -#define BCM2835_GPIO_BASE 0x200000 // Address to GPIO register file -#define BCM2835_SPI0_BASE 0x204000 // Address to SPI0 register file -#define BCM2835_TIMER_BASE 0x3000 // Address to System Timer register file - -#define BCM2835_SPI0_CS_RXF 0x00100000 // Receive FIFO is full -#define BCM2835_SPI0_CS_RXR 0x00080000 // FIFO needs reading -#define BCM2835_SPI0_CS_TXD 0x00040000 // TXD TX FIFO can accept Data -#define BCM2835_SPI0_CS_RXD 0x00020000 // RXD RX FIFO contains Data -#define BCM2835_SPI0_CS_DONE 0x00010000 // Done transfer Done -#define BCM2835_SPI0_CS_ADCS 0x00000800 // Automatically Deassert Chip Select -#define BCM2835_SPI0_CS_INTR 0x00000400 // Fire interrupts on RXR? -#define BCM2835_SPI0_CS_INTD 0x00000200 // Fire interrupts on DONE? -#define BCM2835_SPI0_CS_DMAEN 0x00000100 // Enable DMA transfers? -#define BCM2835_SPI0_CS_TA 0x00000080 // Transfer Active -#define BCM2835_SPI0_CS_CLEAR 0x00000030 // Clear FIFO Clear RX and TX -#define BCM2835_SPI0_CS_CLEAR_RX 0x00000020 // Clear FIFO Clear RX -#define BCM2835_SPI0_CS_CLEAR_TX 0x00000010 // Clear FIFO Clear TX -#define BCM2835_SPI0_CS_CPOL 0x00000008 // Clock Polarity -#define BCM2835_SPI0_CS_CPHA 0x00000004 // Clock Phase -#define BCM2835_SPI0_CS_CS 0x00000003 // Chip Select - -#define BCM2835_SPI0_CS_RXF_SHIFT 20 -#define BCM2835_SPI0_CS_RXR_SHIFT 19 -#define BCM2835_SPI0_CS_TXD_SHIFT 18 -#define BCM2835_SPI0_CS_RXD_SHIFT 17 -#define BCM2835_SPI0_CS_DONE_SHIFT 16 -#define BCM2835_SPI0_CS_ADCS_SHIFT 11 -#define BCM2835_SPI0_CS_INTR_SHIFT 10 -#define BCM2835_SPI0_CS_INTD_SHIFT 9 -#define BCM2835_SPI0_CS_DMAEN_SHIFT 8 -#define BCM2835_SPI0_CS_TA_SHIFT 7 -#define BCM2835_SPI0_CS_CLEAR_RX_SHIFT 5 -#define BCM2835_SPI0_CS_CLEAR_TX_SHIFT 4 -#define BCM2835_SPI0_CS_CPOL_SHIFT 3 -#define BCM2835_SPI0_CS_CPHA_SHIFT 2 -#define BCM2835_SPI0_CS_CS_SHIFT 0 - -#define GPIO_SPI0_MOSI 10 // Pin P1-19, MOSI when SPI0 in use -#define GPIO_SPI0_MISO 9 // Pin P1-21, MISO when SPI0 in use -#define GPIO_SPI0_CLK 11 // Pin P1-23, CLK when SPI0 in use -#define GPIO_SPI0_CE0 8 // Pin P1-24, CE0 when SPI0 in use -#define GPIO_SPI0_CE1 7 // Pin P1-26, CE1 when SPI0 in use - -extern volatile void *bcm2835; - -typedef struct GPIORegisterFile -{ - uint32_t gpfsel[6], reserved0; // GPIO Function Select registers, 3 bits per pin, 10 pins in an uint32_t - uint32_t gpset[2], reserved1; // GPIO Pin Output Set registers, write a 1 to bit at index I to set the pin at index I high - uint32_t gpclr[2], reserved2; // GPIO Pin Output Clear registers, write a 1 to bit at index I to set the pin at index I low - uint32_t gplev[2]; -} GPIORegisterFile; -extern volatile GPIORegisterFile *gpio; - -#define SET_GPIO_MODE(pin, mode) gpio->gpfsel[(pin)/10] = (gpio->gpfsel[(pin)/10] & ~(0x7 << ((pin) % 10) * 3)) | ((mode) << ((pin) % 10) * 3) -#define GET_GPIO_MODE(pin) ((gpio->gpfsel[(pin)/10] & (0x7 << ((pin) % 10) * 3)) >> (((pin) % 10) * 3)) -#define GET_GPIO(pin) (gpio->gplev[0] & (1 << (pin))) // Pin must be (0-31) -#define SET_GPIO(pin) gpio->gpset[0] = 1 << (pin) // Pin must be (0-31) -#define CLEAR_GPIO(pin) gpio->gpclr[0] = 1 << (pin) // Pin must be (0-31) - -typedef struct SPIRegisterFile -{ - uint32_t cs; // SPI Master Control and Status register - uint32_t fifo; // SPI Master TX and RX FIFOs - uint32_t clk; // SPI Master Clock Divider - uint32_t dlen; // SPI Master Number of DMA Bytes to Write -} SPIRegisterFile; -extern volatile SPIRegisterFile *spi; - -// Defines the size of the SPI task memory buffer in bytes. This memory buffer can contain two frames worth of tasks at maximum, -// so for best performance, should be at least ~DISPLAY_WIDTH*DISPLAY_HEIGHT*BYTES_PER_PIXEL*2 bytes in size, plus some small -// amount for structuring each SPITask command. Technically this can be something very small, like 4096b, and not need to contain -// even a single full frame of data, but such small buffers can cause performance issues from threads starving. -#define SHARED_MEMORY_SIZE (DISPLAY_DRAWABLE_WIDTH*DISPLAY_DRAWABLE_HEIGHT*SPI_BYTESPERPIXEL*3) -#define SPI_QUEUE_SIZE (SHARED_MEMORY_SIZE - sizeof(SharedMemory)) - -#if defined(SPI_3WIRE_DATA_COMMAND_FRAMING_BITS) && SPI_3WIRE_DATA_COMMAND_FRAMING_BITS == 1 -// Need a byte of padding for 8-bit -> 9-bit expansion for performance -#define SPI_9BIT_TASK_PADDING_BYTES 1 -#else -#define SPI_9BIT_TASK_PADDING_BYTES 0 -#endif - -// Defines the maximum size of a single SPI task, in bytes. This excludes the command byte. If MAX_SPI_TASK_SIZE -// is not defined, there is no length limit that applies. (In ALL_TASKS_SHOULD_DMA version of DMA transfer, -// there is DMA chaining, so SPI tasks can be arbitrarily long) -#ifndef ALL_TASKS_SHOULD_DMA -#define MAX_SPI_TASK_SIZE 65528 -#endif - -typedef struct __attribute__((packed)) SPITask -{ - uint32_t size; // Size, including both 8-bit and 9-bit tasks -#ifdef SPI_3WIRE_PROTOCOL - uint32_t sizeExpandedTaskWithPadding; // Size of the expanded 9-bit/32-bit task. The expanded task starts at address spiTask->data + spiTask->size - spiTask->sizeExpandedTaskWithPadding; -#endif -#ifdef SPI_32BIT_COMMANDS - uint32_t cmd; -#else - uint8_t cmd; -#endif - uint32_t dmaSpiHeader; -#ifdef OFFLOAD_PIXEL_COPY_TO_DMA_CPP - uint8_t *fb; - uint8_t *prevFb; - uint16_t width; -#endif - uint8_t data[]; // Contains both 8-bit and 9-bit tasks back to back, 8-bit first, then 9-bit. - -#ifdef SPI_3WIRE_PROTOCOL - inline uint8_t *PayloadStart() { return data + (size - sizeExpandedTaskWithPadding); } - inline uint8_t *PayloadEnd() { return data + (size - SPI_9BIT_TASK_PADDING_BYTES); } - inline uint32_t PayloadSize() const { return sizeExpandedTaskWithPadding - SPI_9BIT_TASK_PADDING_BYTES; } - inline uint32_t *DmaSpiHeaderAddress() { return (uint32_t*)(PayloadStart()-4); } -#else - inline uint8_t *PayloadStart() { return data; } - inline uint8_t *PayloadEnd() { return data + size; } - inline uint32_t PayloadSize() const { return size; } - inline uint32_t *DmaSpiHeaderAddress() { return &dmaSpiHeader; } -#endif - -} SPITask; - -#define BEGIN_SPI_COMMUNICATION() do { spi->cs = BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; } while(0) -#define END_SPI_COMMUNICATION() do { \ - uint32_t cs; \ - while (!(((cs = spi->cs) ^ BCM2835_SPI0_CS_TA) & (BCM2835_SPI0_CS_DONE | BCM2835_SPI0_CS_TA))) /* While TA=1 and DONE=0*/ \ - { \ - if ((cs & (BCM2835_SPI0_CS_RXR | BCM2835_SPI0_CS_RXF))) \ - spi->cs = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; \ - } \ - spi->cs = BCM2835_SPI0_CS_CLEAR_RX | DISPLAY_SPI_DRIVE_SETTINGS; /* Clear TA and any pending bytes */ \ - } while(0) - -#define WAIT_SPI_FINISHED() do { \ - uint32_t cs; \ - while (!((cs = spi->cs) & BCM2835_SPI0_CS_DONE)) /* While DONE=0*/ \ - { \ - if ((cs & (BCM2835_SPI0_CS_RXR | BCM2835_SPI0_CS_RXF))) \ - spi->cs = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; \ - } \ - } while(0) - - -// A convenience for defining and dispatching SPI task bytes inline -#define SPI_TRANSFER(command, ...) do { \ - char data_buffer[] = { __VA_ARGS__ }; \ - SPITask *t = AllocTask(sizeof(data_buffer)); \ - t->cmd = (command); \ - memcpy(t->data, data_buffer, sizeof(data_buffer)); \ - CommitTask(t); \ - RunSPITask(t); \ - DoneTask(t); \ - } while(0) - -#define QUEUE_SPI_TRANSFER(command, ...) do { \ - char data_buffer[] = { __VA_ARGS__ }; \ - SPITask *t = AllocTask(sizeof(data_buffer)); \ - t->cmd = (command); \ - memcpy(t->data, data_buffer, sizeof(data_buffer)); \ - CommitTask(t); \ - } while(0) - -#ifdef DISPLAY_SPI_BUS_IS_16BITS_WIDE // For displays that have their command register set be 16 bits word size width (ILI9486) - -#define QUEUE_MOVE_CURSOR_TASK(cursor, pos) do { \ - SPITask *task = AllocTask(4); \ - task->cmd = (cursor); \ - task->data[0] = 0; \ - task->data[1] = (pos) >> 8; \ - task->data[2] = 0; \ - task->data[3] = (pos) & 0xFF; \ - bytesTransferred += 6; \ - CommitTask(task); \ - } while(0) - -#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ - SPITask *task = AllocTask(8); \ - task->cmd = (cursor); \ - task->data[0] = 0; \ - task->data[1] = (x) >> 8; \ - task->data[2] = 0; \ - task->data[3] = (x) & 0xFF; \ - task->data[4] = 0; \ - task->data[5] = (endX) >> 8; \ - task->data[6] = 0; \ - task->data[7] = (endX) & 0xFF; \ - bytesTransferred += 10; \ - CommitTask(task); \ - } while(0) - -#elif defined(DISPLAY_SET_CURSOR_IS_8_BIT) // For displays that have their set cursor commands be a uint8 instead of uint16 (SSD1351) - -#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ - SPITask *task = AllocTask(2); \ - task->cmd = (cursor); \ - task->data[0] = (x); \ - task->data[1] = (endX); \ - bytesTransferred += 3; \ - CommitTask(task); \ - } while(0) - -#else // Regular 8-bit interface with 16bits wide set cursor commands (most displays) - -#define QUEUE_MOVE_CURSOR_TASK(cursor, pos) do { \ - SPITask *task = AllocTask(2); \ - task->cmd = (cursor); \ - task->data[0] = (pos) >> 8; \ - task->data[1] = (pos) & 0xFF; \ - bytesTransferred += 3; \ - CommitTask(task); \ - } while(0) - -#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ - SPITask *task = AllocTask(4); \ - task->cmd = (cursor); \ - task->data[0] = (x) >> 8; \ - task->data[1] = (x) & 0xFF; \ - task->data[2] = (endX) >> 8; \ - task->data[3] = (endX) & 0xFF; \ - bytesTransferred += 5; \ - CommitTask(task); \ - } while(0) -#endif - -typedef struct SharedMemory -{ -#ifdef USE_DMA_TRANSFERS - volatile DMAControlBlock cb[2]; - volatile uint32_t dummyDMADestinationWriteAddress; - volatile uint32_t dmaTxChannel, dmaRxChannel; -#endif - volatile uint32_t queueHead; - volatile uint32_t queueTail; - volatile uint32_t spiBytesQueued; // Number of actual payload bytes in the queue - volatile uint32_t interruptsRaised; - volatile uintptr_t sharedMemoryBaseInPhysMemory; - volatile uint8_t buffer[]; -} SharedMemory; - -#ifdef KERNEL_MODULE -extern dma_addr_t spiTaskMemoryPhysical; -#define VIRT_TO_BUS(ptr) ((uintptr_t)(ptr) | 0xC0000000U) -#endif -extern SharedMemory *spiTaskMemory; -extern double spiUsecsPerByte; - -extern SharedMemory *dmaSourceMemory; // TODO: Optimize away the need to have this at all, instead DMA directly from SPI ring buffer if possible - -#ifdef STATISTICS -extern volatile uint64_t spiThreadIdleUsecs; -extern volatile uint64_t spiThreadSleepStartTime; -extern volatile int spiThreadSleeping; -#endif - -extern int mem_fd; - -#ifdef SPI_3WIRE_PROTOCOL - -// Converts the given SPI task in-place from an 8-bit task to a 9-bit task. -void Interleave8BitSPITaskTo9Bit(SPITask *task); - -// Converts the given SPI task in-place from a 16-bit task to a 32-bit task. -void Interleave16BitSPITaskTo32Bit(SPITask *task); - -// If the given display is a 3-wire SPI display (9 bits/task instead of 8 bits/task), this function computes the byte size of the 8-bit task when it is converted to a 9-bit task. -uint32_t NumBytesNeededFor9BitSPITask(uint32_t byteSizeFor8BitTask); - -// If the given display is a 3-wire SPI display with 32 bits bus width, this function computes the byte size of the task when it is converted to a 32-bit task. -uint32_t NumBytesNeededFor32BitSPITask(uint32_t byteSizeFor8BitTask); - -#endif - -static inline SPITask *AllocTask(uint32_t bytes) // Returns a pointer to a new SPI task block, called on main thread -{ -#ifdef SPI_3WIRE_PROTOCOL - // For 3-wire/9-bit tasks, store the converted task right at the end of the 8-bit task. -#ifdef SPI_32BIT_COMMANDS - uint32_t sizeExpandedTaskWithPadding = NumBytesNeededFor32BitSPITask(bytes) + SPI_9BIT_TASK_PADDING_BYTES; -#else - uint32_t sizeExpandedTaskWithPadding = NumBytesNeededFor9BitSPITask(bytes) + SPI_9BIT_TASK_PADDING_BYTES; -#endif - bytes += sizeExpandedTaskWithPadding; -#else -// const uint32_t totalBytesFor9BitTask = 0; -#endif - - uint32_t bytesToAllocate = sizeof(SPITask) + bytes;// + totalBytesFor9BitTask; - uint32_t tail = spiTaskMemory->queueTail; - uint32_t newTail = tail + bytesToAllocate; - // Is the new task too large to write contiguously into the ring buffer, that it's split into two parts? We never split, - // but instead write a sentinel at the end of the ring buffer, and jump the tail back to the beginning of the buffer and - // allocate the new task there. However in doing so, we must make sure that we don't write over the head marker. - if (newTail + sizeof(SPITask)/*Add extra SPITask size so that there will always be room for eob marker*/ >= SPI_QUEUE_SIZE) - { - uint32_t head = spiTaskMemory->queueHead; - // Write a sentinel, but wait for the head to advance first so that it is safe to write. - while(head > tail || head == 0/*Head must move > 0 so that we don't stomp on it*/) - { -#if defined(KERNEL_MODULE_CLIENT) && !defined(KERNEL_MODULE) - // Hack: Pump the kernel module to start transferring in case it has stopped. TODO: Remove this line: - if (!(spi->cs & BCM2835_SPI0_CS_TA)) spi->cs |= BCM2835_SPI0_CS_TA; - // Wait until there are no remaining bytes to process in the far right end of the buffer - we'll write an eob marker there as soon as the read pointer has cleared it. - // At this point the SPI queue may actually be quite empty, so don't sleep (except for now in kernel client app) - usleep(100); -#endif - head = spiTaskMemory->queueHead; - } - SPITask *endOfBuffer = (SPITask*)(spiTaskMemory->buffer + tail); - endOfBuffer->cmd = 0; // Use cmd=0x00 to denote "end of buffer, wrap to beginning" - __sync_synchronize(); - spiTaskMemory->queueTail = 0; - __sync_synchronize(); -#if !defined(KERNEL_MODULE_CLIENT) && !defined(KERNEL_MODULE) - if (spiTaskMemory->queueHead == tail) syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAKE, 1, 0, 0, 0); // Wake the SPI thread if it was sleeping to get new tasks -#endif - tail = 0; - newTail = bytesToAllocate; - } - - // If the SPI task queue is full, wait for the SPI thread to process some tasks. This throttles the main thread to not run too fast. - uint32_t head = spiTaskMemory->queueHead; - while(head > tail && head <= newTail) - { -#if defined(KERNEL_MODULE_CLIENT) && !defined(KERNEL_MODULE) - // Hack: Pump the kernel module to start transferring in case it has stopped. TODO: Remove this line: - if (!(spi->cs & BCM2835_SPI0_CS_TA)) spi->cs |= BCM2835_SPI0_CS_TA; -#endif - usleep(100); // Since the SPI queue is full, we can afford to sleep a bit on the main thread without introducing lag. - head = spiTaskMemory->queueHead; - } - - SPITask *task = (SPITask*)(spiTaskMemory->buffer + tail); - task->size = bytes; -#ifdef SPI_3WIRE_PROTOCOL - task->sizeExpandedTaskWithPadding = sizeExpandedTaskWithPadding; -#endif -#ifdef OFFLOAD_PIXEL_COPY_TO_DMA_CPP - task->fb = &task->data[0]; - task->prevFb = 0; -#endif - return task; -} - -static inline void CommitTask(SPITask *task) // Advertises the given SPI task from main thread to worker, called on main thread -{ -#ifdef SPI_3WIRE_PROTOCOL -#ifdef SPI_32BIT_COMMANDS - Interleave16BitSPITaskTo32Bit(task); -#else - Interleave8BitSPITaskTo9Bit(task); -#endif -#endif - __sync_synchronize(); -#if !defined(KERNEL_MODULE_CLIENT) && !defined(KERNEL_MODULE) - uint32_t tail = spiTaskMemory->queueTail; -#endif - spiTaskMemory->queueTail = (uint32_t)((uint8_t*)task - spiTaskMemory->buffer) + sizeof(SPITask) + task->size; - __atomic_fetch_add(&spiTaskMemory->spiBytesQueued, task->PayloadSize()+1, __ATOMIC_RELAXED); - __sync_synchronize(); -#if !defined(KERNEL_MODULE_CLIENT) && !defined(KERNEL_MODULE) - if (spiTaskMemory->queueHead == tail) syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAKE, 1, 0, 0, 0); // Wake the SPI thread if it was sleeping to get new tasks -#endif -} - -#ifdef USE_SPI_THREAD -#define IN_SINGLE_THREADED_MODE_RUN_TASK() ((void)0) -#else -#define IN_SINGLE_THREADED_MODE_RUN_TASK() { \ - SPITask *t = GetTask(); \ - RunSPITask(t); \ - DoneTask(t); \ -} -#endif - -int InitSPI(void); -void DeinitSPI(void); -void ExecuteSPITasks(void); -void RunSPITask(SPITask *task); -SPITask *GetTask(void); -void DoneTask(SPITask *task); -void DumpSPICS(uint32_t reg); -#ifdef RUN_WITH_REALTIME_THREAD_PRIORITY -void SetRealtimeThreadPriority(); -#endif +#pragma once + +#define BCM2835_GPIO_BASE 0x200000 // Address to GPIO register file +#define BCM2835_SPI0_BASE 0x204000 // Address to SPI0 register file +#define BCM2835_TIMER_BASE 0x3000 // Address to System Timer register file + +#define BCM2835_SPI0_CS_RXF 0x00100000 // Receive FIFO is full +#define BCM2835_SPI0_CS_RXR 0x00080000 // FIFO needs reading +#define BCM2835_SPI0_CS_TXD 0x00040000 // TXD TX FIFO can accept Data +#define BCM2835_SPI0_CS_RXD 0x00020000 // RXD RX FIFO contains Data +#define BCM2835_SPI0_CS_DONE 0x00010000 // Done transfer Done +#define BCM2835_SPI0_CS_ADCS 0x00000800 // Automatically Deassert Chip Select +#define BCM2835_SPI0_CS_INTR 0x00000400 // Fire interrupts on RXR? +#define BCM2835_SPI0_CS_INTD 0x00000200 // Fire interrupts on DONE? +#define BCM2835_SPI0_CS_DMAEN 0x00000100 // Enable DMA transfers? +#define BCM2835_SPI0_CS_TA 0x00000080 // Transfer Active +#define BCM2835_SPI0_CS_CLEAR 0x00000030 // Clear FIFO Clear RX and TX +#define BCM2835_SPI0_CS_CLEAR_RX 0x00000020 // Clear FIFO Clear RX +#define BCM2835_SPI0_CS_CLEAR_TX 0x00000010 // Clear FIFO Clear TX +#define BCM2835_SPI0_CS_CPOL 0x00000008 // Clock Polarity +#define BCM2835_SPI0_CS_CPHA 0x00000004 // Clock Phase +#define BCM2835_SPI0_CS_CS 0x00000003 // Chip Select + +#define BCM2835_SPI0_CS_RXF_SHIFT 20 +#define BCM2835_SPI0_CS_RXR_SHIFT 19 +#define BCM2835_SPI0_CS_TXD_SHIFT 18 +#define BCM2835_SPI0_CS_RXD_SHIFT 17 +#define BCM2835_SPI0_CS_DONE_SHIFT 16 +#define BCM2835_SPI0_CS_ADCS_SHIFT 11 +#define BCM2835_SPI0_CS_INTR_SHIFT 10 +#define BCM2835_SPI0_CS_INTD_SHIFT 9 +#define BCM2835_SPI0_CS_DMAEN_SHIFT 8 +#define BCM2835_SPI0_CS_TA_SHIFT 7 +#define BCM2835_SPI0_CS_CLEAR_RX_SHIFT 5 +#define BCM2835_SPI0_CS_CLEAR_TX_SHIFT 4 +#define BCM2835_SPI0_CS_CPOL_SHIFT 3 +#define BCM2835_SPI0_CS_CPHA_SHIFT 2 +#define BCM2835_SPI0_CS_CS_SHIFT 0 + +#define GPIO_SPI0_MOSI 10 // Pin P1-19, MOSI when SPI0 in use +#define GPIO_SPI0_MISO 9 // Pin P1-21, MISO when SPI0 in use +#define GPIO_SPI0_INTR 25 // Pin P1-22, INTR when SPI0 in use +#define GPIO_SPI0_CLK 11 // Pin P1-23, CLK when SPI0 in use +#define GPIO_SPI0_CE0 8 // Pin P1-24, CE0 when SPI0 in use +#define GPIO_SPI0_CE1 7 // Pin P1-26, CE1 when SPI0 in use + +typedef struct SharedMemory +{ +#ifdef USE_DMA_TRANSFERS + volatile DMAControlBlock cb[2]; + volatile uint32_t dummyDMADestinationWriteAddress; + volatile uint32_t dmaTxChannel, dmaRxChannel; +#endif + volatile uint32_t queueHead; + volatile uint32_t queueTail; + volatile uint32_t spiBytesQueued; // Number of actual payload bytes in the queue + volatile uint32_t interruptsRaised; + volatile uintptr_t sharedMemoryBaseInPhysMemory; + volatile uint8_t buffer[]; +} SharedMemory; + +extern SharedMemory *dmaSourceMemory; // TODO: Optimize away the need to have this at all, instead DMA directly from SPI ring buffer if possible + +extern SharedMemory *spiFlagemory; +extern SharedMemory *spiTaskMemory; +extern double spiUsecsPerByte; + +extern int mem_fd; + diff --git a/spi_kernel.h b/spi_kernel.h new file mode 100644 index 0000000..530fc1c --- /dev/null +++ b/spi_kernel.h @@ -0,0 +1,7 @@ +#pragma once + +#include "spi.h" + +//#include +//#include +#define VIRT_TO_BUS(ptr) ((uintptr_t)(ptr) | 0xC0000000U) diff --git a/spi_user.h b/spi_user.h new file mode 100644 index 0000000..9790f4f --- /dev/null +++ b/spi_user.h @@ -0,0 +1,317 @@ +#pragma once + +#include +#include // FUTEX_WAKE +#include +#include "dma.h" +#include "spi.h" +#include "display.h" +#include "tick.h" +#include "display.h" + +// Sends no-Op command wich enables sensing of touch screen on some displays +extern void sendNoOpCommand(); + +// Checks last time touch screen sensed +extern bool activeTouchscreen(); + +extern volatile void *bcm2835; + +typedef struct GPIORegisterFile +{ + uint32_t gpfsel[6], reserved0; // GPIO Function Select registers, 3 bits per pin, 10 pins in an uint32_t + uint32_t gpset[2], reserved1; // GPIO Pin Output Set registers, write a 1 to bit at index I to set the pin at index I high + uint32_t gpclr[2], reserved2; // GPIO Pin Output Clear registers, write a 1 to bit at index I to set the pin at index I low + uint32_t gplev[2]; +} GPIORegisterFile; +extern volatile GPIORegisterFile *gpio; + +#define SET_GPIO_MODE(pin, mode) gpio->gpfsel[(pin)/10] = (gpio->gpfsel[(pin)/10] & ~(0x7 << ((pin) % 10) * 3)) | ((mode) << ((pin) % 10) * 3) +#define GET_GPIO_MODE(pin) ((gpio->gpfsel[(pin)/10] & (0x7 << ((pin) % 10) * 3)) >> (((pin) % 10) * 3)) +#define GET_GPIO(pin) (gpio->gplev[0] & (1 << (pin))) // Pin must be (0-31) +#define SET_GPIO(pin) gpio->gpset[0] = 1 << (pin) // Pin must be (0-31) +#define CLEAR_GPIO(pin) gpio->gpclr[0] = 1 << (pin) // Pin must be (0-31) + +typedef struct SPIRegisterFile +{ + uint32_t cs; // SPI Master Control and Status register + uint32_t fifo; // SPI Master TX and RX FIFOs + uint32_t clk; // SPI Master Clock Divider + uint32_t dlen; // SPI Master Number of DMA Bytes to Write +} SPIRegisterFile; +extern volatile SPIRegisterFile *spi; + +// Defines the size of the SPI task memory buffer in bytes. This memory buffer can contain two frames worth of tasks at maximum, +// so for best performance, should be at least ~DISPLAY_WIDTH*DISPLAY_HEIGHT*BYTES_PER_PIXEL*2 bytes in size, plus some small +// amount for structuring each SPITask command. Technically this can be something very small, like 4096b, and not need to contain +// even a single full frame of data, but such small buffers can cause performance issues from threads starving. +#define SHARED_MEMORY_SIZE (DISPLAY_DRAWABLE_WIDTH*DISPLAY_DRAWABLE_HEIGHT*SPI_BYTESPERPIXEL*3) +#define SPI_QUEUE_SIZE (SHARED_MEMORY_SIZE - sizeof(SharedMemory)) + +#if defined(SPI_3WIRE_DATA_COMMAND_FRAMING_BITS) && SPI_3WIRE_DATA_COMMAND_FRAMING_BITS == 1 +// Need a byte of padding for 8-bit -> 9-bit expansion for performance +#define SPI_9BIT_TASK_PADDING_BYTES 1 +#else +#define SPI_9BIT_TASK_PADDING_BYTES 0 +#endif + +// Defines the maximum size of a single SPI task, in bytes. This excludes the command byte. If MAX_SPI_TASK_SIZE +// is not defined, there is no length limit that applies. (In ALL_TASKS_SHOULD_DMA version of DMA transfer, +// there is DMA chaining, so SPI tasks can be arbitrarily long) +#ifndef ALL_TASKS_SHOULD_DMA +#define MAX_SPI_TASK_SIZE 65528 +#endif + +typedef struct __attribute__((packed)) SPITask +{ + uint32_t size; // Size, including both 8-bit and 9-bit tasks +#ifdef SPI_3WIRE_PROTOCOL + uint32_t sizeExpandedTaskWithPadding; // Size of the expanded 9-bit/32-bit task. The expanded task starts at address spiTask->data + spiTask->size - spiTask->sizeExpandedTaskWithPadding; +#endif +#ifdef SPI_32BIT_COMMANDS + uint32_t cmd; +#else + uint8_t cmd; +#endif + uint32_t dmaSpiHeader; +#ifdef OFFLOAD_PIXEL_COPY_TO_DMA_CPP + uint8_t *fb; + uint8_t *prevFb; + uint16_t width; +#endif + uint8_t data[]; // Contains both 8-bit and 9-bit tasks back to back, 8-bit first, then 9-bit. + +#ifdef SPI_3WIRE_PROTOCOL + inline uint8_t *PayloadStart() { return data + (size - sizeExpandedTaskWithPadding); } + inline uint8_t *PayloadEnd() { return data + (size - SPI_9BIT_TASK_PADDING_BYTES); } + inline uint32_t PayloadSize() const { return sizeExpandedTaskWithPadding - SPI_9BIT_TASK_PADDING_BYTES; } + inline uint32_t *DmaSpiHeaderAddress() { return (uint32_t*)(PayloadStart()-4); } +#else + inline uint8_t *PayloadStart() { return data; } + inline uint8_t *PayloadEnd() { return data + size; } + inline uint32_t PayloadSize() const { return size; } + inline uint32_t *DmaSpiHeaderAddress() { return &dmaSpiHeader; } +#endif + +} SPITask; + +#define BEGIN_SPI_COMMUNICATION() do { spi->cs = BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; } while(0) +#define END_SPI_COMMUNICATION() do { \ + uint32_t cs; \ + while (!(((cs = spi->cs) ^ BCM2835_SPI0_CS_TA) & (BCM2835_SPI0_CS_DONE | BCM2835_SPI0_CS_TA))) /* While TA=1 and DONE=0*/ \ + { \ + if ((cs & (BCM2835_SPI0_CS_RXR | BCM2835_SPI0_CS_RXF))) \ + spi->cs = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; \ + } \ + spi->cs = BCM2835_SPI0_CS_CLEAR_RX | DISPLAY_SPI_DRIVE_SETTINGS; /* Clear TA and any pending bytes */ \ + } while(0) + +#define WAIT_SPI_FINISHED() do { \ + uint32_t cs; \ + while (!((cs = spi->cs) & BCM2835_SPI0_CS_DONE)) /* While DONE=0*/ \ + { \ + if ((cs & (BCM2835_SPI0_CS_RXR | BCM2835_SPI0_CS_RXF))) \ + spi->cs = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_TA | DISPLAY_SPI_DRIVE_SETTINGS; \ + } \ + } while(0) + + +// A convenience for defining and dispatching SPI task bytes inline +#define SPI_TRANSFER(command, ...) do { \ + char data_buffer[] = { __VA_ARGS__ }; \ + SPITask *t = AllocTask(sizeof(data_buffer)); \ + t->cmd = (command); \ + memcpy(t->data, data_buffer, sizeof(data_buffer)); \ + CommitTask(t); \ + RunSPITask(t); \ + DoneTask(t); \ + } while(0) + +#define QUEUE_SPI_TRANSFER(command, ...) do { \ + char data_buffer[] = { __VA_ARGS__ }; \ + SPITask *t = AllocTask(sizeof(data_buffer)); \ + t->cmd = (command); \ + memcpy(t->data, data_buffer, sizeof(data_buffer)); \ + CommitTask(t); \ + } while(0) + +#ifdef DISPLAY_SPI_BUS_IS_16BITS_WIDE // For displays that have their command register set be 16 bits word size width (ILI9486) + +#define QUEUE_MOVE_CURSOR_TASK(cursor, pos) do { \ + SPITask *task = AllocTask(4); \ + task->cmd = (cursor); \ + task->data[0] = 0; \ + task->data[1] = (pos) >> 8; \ + task->data[2] = 0; \ + task->data[3] = (pos) & 0xFF; \ + bytesTransferred += 6; \ + CommitTask(task); \ + } while(0) + +#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ + SPITask *task = AllocTask(8); \ + task->cmd = (cursor); \ + task->data[0] = 0; \ + task->data[1] = (x) >> 8; \ + task->data[2] = 0; \ + task->data[3] = (x) & 0xFF; \ + task->data[4] = 0; \ + task->data[5] = (endX) >> 8; \ + task->data[6] = 0; \ + task->data[7] = (endX) & 0xFF; \ + bytesTransferred += 10; \ + CommitTask(task); \ + } while(0) + +#elif defined(DISPLAY_SET_CURSOR_IS_8_BIT) // For displays that have their set cursor commands be a uint8 instead of uint16 (SSD1351) + +#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ + SPITask *task = AllocTask(2); \ + task->cmd = (cursor); \ + task->data[0] = (x); \ + task->data[1] = (endX); \ + bytesTransferred += 3; \ + CommitTask(task); \ + } while(0) + +#else // Regular 8-bit interface with 16bits wide set cursor commands (most displays) + +#define QUEUE_MOVE_CURSOR_TASK(cursor, pos) do { \ + SPITask *task = AllocTask(2); \ + task->cmd = (cursor); \ + task->data[0] = (pos) >> 8; \ + task->data[1] = (pos) & 0xFF; \ + bytesTransferred += 3; \ + CommitTask(task); \ + } while(0) + +#define QUEUE_SET_WRITE_WINDOW_TASK(cursor, x, endX) do { \ + SPITask *task = AllocTask(4); \ + task->cmd = (cursor); \ + task->data[0] = (x) >> 8; \ + task->data[1] = (x) & 0xFF; \ + task->data[2] = (endX) >> 8; \ + task->data[3] = (endX) & 0xFF; \ + bytesTransferred += 5; \ + CommitTask(task); \ + } while(0) +#endif + +#ifdef STATISTICS +extern volatile uint64_t spiThreadIdleUsecs; +extern volatile uint64_t spiThreadSleepStartTime; +extern volatile int spiThreadSleeping; +#endif + +#ifdef SPI_3WIRE_PROTOCOL + +// Converts the given SPI task in-place from an 8-bit task to a 9-bit task. +void Interleave8BitSPITaskTo9Bit(SPITask *task); + +// Converts the given SPI task in-place from a 16-bit task to a 32-bit task. +void Interleave16BitSPITaskTo32Bit(SPITask *task); + +// If the given display is a 3-wire SPI display (9 bits/task instead of 8 bits/task), this function computes the byte size of the 8-bit task when it is converted to a 9-bit task. +uint32_t NumBytesNeededFor9BitSPITask(uint32_t byteSizeFor8BitTask); + +// If the given display is a 3-wire SPI display with 32 bits bus width, this function computes the byte size of the task when it is converted to a 32-bit task. +uint32_t NumBytesNeededFor32BitSPITask(uint32_t byteSizeFor8BitTask); + +#endif + +static inline SPITask *AllocTask(uint32_t bytes) // Returns a pointer to a new SPI task block, called on main thread +{ +#ifdef SPI_3WIRE_PROTOCOL + // For 3-wire/9-bit tasks, store the converted task right at the end of the 8-bit task. +#ifdef SPI_32BIT_COMMANDS + uint32_t sizeExpandedTaskWithPadding = NumBytesNeededFor32BitSPITask(bytes) + SPI_9BIT_TASK_PADDING_BYTES; +#else + uint32_t sizeExpandedTaskWithPadding = NumBytesNeededFor9BitSPITask(bytes) + SPI_9BIT_TASK_PADDING_BYTES; +#endif + bytes += sizeExpandedTaskWithPadding; +#else +// const uint32_t totalBytesFor9BitTask = 0; +#endif + + uint32_t bytesToAllocate = sizeof(SPITask) + bytes;// + totalBytesFor9BitTask; + uint32_t tail = spiTaskMemory->queueTail; + uint32_t newTail = tail + bytesToAllocate; + // Is the new task too large to write contiguously into the ring buffer, that it's split into two parts? We never split, + // but instead write a sentinel at the end of the ring buffer, and jump the tail back to the beginning of the buffer and + // allocate the new task there. However in doing so, we must make sure that we don't write over the head marker. + if (newTail + sizeof(SPITask)/*Add extra SPITask size so that there will always be room for eob marker*/ >= SPI_QUEUE_SIZE) + { + uint32_t head = spiTaskMemory->queueHead; + // Write a sentinel, but wait for the head to advance first so that it is safe to write. + while(head > tail || head == 0/*Head must move > 0 so that we don't stomp on it*/) + { + head = spiTaskMemory->queueHead; + } + SPITask *endOfBuffer = (SPITask*)(spiTaskMemory->buffer + tail); + endOfBuffer->cmd = 0; // Use cmd=0x00 to denote "end of buffer, wrap to beginning" + __sync_synchronize(); + spiTaskMemory->queueTail = 0; + __sync_synchronize(); + if (spiTaskMemory->queueHead == tail) syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAKE, 1, 0, 0, 0); // Wake the SPI thread if it was sleeping to get new tasks + tail = 0; + newTail = bytesToAllocate; + } + + // If the SPI task queue is full, wait for the SPI thread to process some tasks. This throttles the main thread to not run too fast. + uint32_t head = spiTaskMemory->queueHead; + while(head > tail && head <= newTail) + { + usleep(100); // Since the SPI queue is full, we can afford to sleep a bit on the main thread without introducing lag. + head = spiTaskMemory->queueHead; + } + + SPITask *task = (SPITask*)(spiTaskMemory->buffer + tail); + task->size = bytes; +#ifdef SPI_3WIRE_PROTOCOL + task->sizeExpandedTaskWithPadding = sizeExpandedTaskWithPadding; +#endif +#ifdef OFFLOAD_PIXEL_COPY_TO_DMA_CPP + task->fb = &task->data[0]; + task->prevFb = 0; +#endif + return task; +} + +static inline void CommitTask(SPITask *task) // Advertises the given SPI task from main thread to worker, called on main thread +{ +#ifdef SPI_3WIRE_PROTOCOL +#ifdef SPI_32BIT_COMMANDS + Interleave16BitSPITaskTo32Bit(task); +#else + Interleave8BitSPITaskTo9Bit(task); +#endif +#endif + __sync_synchronize(); + uint32_t tail = spiTaskMemory->queueTail; + spiTaskMemory->queueTail = (uint32_t)((uint8_t*)task - spiTaskMemory->buffer) + sizeof(SPITask) + task->size; + __atomic_fetch_add(&spiTaskMemory->spiBytesQueued, task->PayloadSize()+1, __ATOMIC_RELAXED); + __sync_synchronize(); + if (spiTaskMemory->queueHead == tail) syscall(SYS_futex, &spiTaskMemory->queueTail, FUTEX_WAKE, 1, 0, 0, 0); // Wake the SPI thread if it was sleeping to get new tasks +} + +#ifdef USE_SPI_THREAD +#define IN_SINGLE_THREADED_MODE_RUN_TASK() ((void)0) +#else +#define IN_SINGLE_THREADED_MODE_RUN_TASK() { \ + SPITask *t = GetTask(); \ + RunSPITask(t); \ + DoneTask(t); \ +} +#endif + +int InitSPI(void); +bool hasInterrupt(); +void DeinitSPI(void); +void ExecuteSPITasks(void); +void RunSPITask(SPITask *task); +SPITask *GetTask(void); +void DoneTask(SPITask *task); +void DumpSPICS(uint32_t reg); +#ifdef RUN_WITH_REALTIME_THREAD_PRIORITY +void SetRealtimeThreadPriority(); +#endif diff --git a/ssd1351.cpp b/ssd1351.cpp index 1417986..f153a01 100644 --- a/ssd1351.cpp +++ b/ssd1351.cpp @@ -2,7 +2,7 @@ #ifdef SSD1351 -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/st7735r.cpp b/st7735r.cpp index ceac386..630a76a 100644 --- a/st7735r.cpp +++ b/st7735r.cpp @@ -2,7 +2,7 @@ #if defined(ST7735R) || defined(ST7735S) || defined(ST7789) -#include "spi.h" +#include "spi_user.h" #include #include diff --git a/statistics.cpp b/statistics.cpp index 8d55a79..0a7ca3c 100644 --- a/statistics.cpp +++ b/statistics.cpp @@ -12,7 +12,7 @@ #include "tick.h" #include "text.h" -#include "spi.h" +#include "spi_user.h" #include "util.h" #include "mailbox.h" #include "mem_alloc.h" diff --git a/util.h b/util.h index e3ce3d3..1665473 100644 --- a/util.h +++ b/util.h @@ -10,6 +10,9 @@ #define ABS(x) ((x) < 0 ? (-(x)) : (x)) #define SWAPU32(x, y) { uint32_t tmp = x; x = y; y = tmp; } + +#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ + } while (0) #ifndef ALIGN_DOWN #define ALIGN_DOWN(ptr, alignment) (((ptr)) & ~((alignment)-1)) diff --git a/util/Makefile b/util/Makefile new file mode 100644 index 0000000..16a7592 --- /dev/null +++ b/util/Makefile @@ -0,0 +1,22 @@ +LIBFLAGS=-L/opt/vc/lib -lbrcmEGL -lbrcmGLESv2 -lbcm_host -lpthread -ljpeg -lshapes +CXXFLAGS=-DDEBUG -fno-exceptions -DPI -std=gnu++11 -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -I.. + +objs=tcCalib.o ../calibrate.o +objs2=tcTest.o + +all: tcCalib tcTest + +clean: + rm -f -- $(objs) tcCalib + rm -f -- $(objs2) tcTest + +../calibrate.o: ../calibrate.cpp + g++ $(CXXFLAGS) -c -o ../calibrate.o ../calibrate.cpp + +tcCalib: $(objs) + g++ -Wall $(CXXFLAGS) $(LIBFLAGS) $(objs) -o tcCalib + +tcTest: $(objs2) + g++ -Wall $(CXXFLAGS) $(LIBFLAGS) $(objs2) -o tcTest + + diff --git a/util/tcCalib.cpp b/util/tcCalib.cpp new file mode 100644 index 0000000..92c779c --- /dev/null +++ b/util/tcCalib.cpp @@ -0,0 +1,366 @@ + + +#include +#include +#include +#include +#include +#include +#include + +#include "VG/openvg.h" +#include "VG/vgu.h" +#include "fontinfo.h" +#include "shapes.h" + +#include +#include +#include + +#include "calibrate.h" + +#define FILE_COFIG (const char *)"/etc/xpt2046.conf" +#define PROC_NAME (char *)"fbcp-ili9341" +#define READ_BUF_SIZE 1024 + +#define POINT_SAMPLES 4 +// Target on-screen points +POINT pointTargets[POINT_SAMPLES] = { + {15, 15}, + {-15, 15}, + {15, -15}, + {-15, -15} +}; + +// Recorded touch points +POINT pointTouches[POINT_SAMPLES]; + +MATRIX calibMatrix; + +#define xHairLines_ROWS 8 +int xHairLines[8][4] = { + 10, 15, 20, 15, // lower left + 15, 10, 15, 20, + -20, 15, -10, 15, // lower right + -15, 10, -15, 20, + 10, -15, 20, -15, // upper left + 15, -10, 15, -20, + -20, -15, -10, -15, // upper right + -15, -10, -15, -20 +}; + +// touch state structure +typedef struct { + int fd; + char* evbuff[80]; + VGfloat x, y, z; + int max_x, max_y; +} touch_t; + +touch_t touch; // global touch state +int left_count = 0; +int quitState = 0; +#define CUR_SIZ 16 // cursor size, pixels beyond centre dot + +// evenThread reads from the touch input file +void *eventThread(void *arg) { +#define OPENDEVICE_TOUCH if ((touch.fd = open("/tmp/TCfifo", O_RDONLY)) < 0) { \ + fprintf(stderr, "Error opening touch!\n"); \ + quitState = 1; \ + return &quitState;} + + // Open touch driver + OPENDEVICE_TOUCH + touch.x = touch.max_x / 2; //Reset touch + touch.y = touch.max_y / 2; + + while (1) { + uint16_t words16read[4]; + usleep(500); + if(read(touch.fd, words16read, sizeof(uint16_t) * 4 )) { + touch.x = words16read[0]; + touch.y = words16read[1]; + touch.z = words16read[2]; + close(touch.fd); + OPENDEVICE_TOUCH + } + } +} + +static int cur_sx, cur_sy, cur_w, cur_h; // cursor location and dimensions +static int cur_saved = 0; // amount of data saved in cursor image backup + +// saveCursor saves the pixels under the touch cursor +void saveCursor(VGImage CursorBuffer, int curx, int cury, int screen_width, int screen_height, int s) { + int sx, sy, ex, ey; + + sx = curx - s; // horizontal + if (sx < 0) { + sx = 0; + } + ex = curx + s; + if (ex > screen_width) { + ex = screen_width; + } + cur_sx = sx; + cur_w = ex - sx; + + sy = cury - s; // vertical + if (sy < 0) { + sy = 0; + } + ey = cury + s; + if (ey > screen_height) { + ey = screen_height; + } + cur_sy = sy; + cur_h = ey - sy; + + vgGetPixels(CursorBuffer, 0, 0, cur_sx, cur_sy, cur_w, cur_h); + cur_saved = cur_w * cur_h; +} + +// restoreCursor restores the pixels under the touch cursor +void restoreCursor(VGImage CursorBuffer) { + if (cur_saved != 0) { + vgSetPixels(cur_sx, cur_sy, CursorBuffer, 0, 0, cur_w, cur_h); + } +} + +// circleCursor draws a translucent circle as the touch cursor +void circleCursor(int curx, int cury, int width, int height, int s) { + Fill(100, 0, 0, 0.50); + Circle(curx, cury, s); + Fill(0, 0, 0, 1); + Circle(curx, cury, 2); +} + +// touchinit starts the touch event thread +int touchinit(int w, int h) { + pthread_t inputThread; + touch.max_x = w; + touch.max_y = h; + return pthread_create(&inputThread, NULL, &eventThread, NULL); +} + +void getTouches(int captures, int width, int height, int &cursorx, int &cursory, VGImage &CursorBuffer) { + int changeCount = 0; + POINT pointCorrected, pointRaw; + int ztouch_thold = 50; + + do { + usleep(10000); // usec - slow loop for other threads + pointRaw.x = touch.x; + pointRaw.y = touch.y; + getDisplayPoint((POINT *)&pointCorrected,(POINT *)&pointRaw,&calibMatrix); + + // Loop until a touch is registered by a change in cursor value + if ((pointCorrected.x != cursorx || pointCorrected.y != cursory) && ((int)touch.z) > ztouch_thold ) { + fprintf(stdout,"actual (%d, %d) => corrected (%d, %d) \n", pointRaw.x, pointRaw.y, pointCorrected.x, pointCorrected.y); + restoreCursor(CursorBuffer); + cursorx = pointCorrected.x; + cursory = pointCorrected.y; + saveCursor(CursorBuffer, cursorx, cursory, width, height, CUR_SIZ); + circleCursor(cursorx, cursory, width, height, CUR_SIZ); + changeCount = changeCount + 1; + touch.z = 0.0; + End(); // update picture + } + } while (changeCount < captures); +} + +void drawBackground(int width, int height) { + Background(0, 0, 0); // Black background + Fill(44, 77, 232, 1); // Big blue marble + Circle(width / 2, 0, width); // The "world" + Fill(255, 255, 255, 1); // White text + TextMid(width / 2, height / 2, "Screen Calibration", SerifTypeface, width / 15); // Greetings + End(); // update picture +} + +void drawCrosshair(int width, int height, int i) { + Stroke(255, 255, 255, 0.5); + StrokeWidth(2); + Line( ( xHairLines[i][0] < 0 ? width + xHairLines[i][0] : xHairLines[i][0] ) + , ( xHairLines[i][1] < 0 ? height + xHairLines[i][1] : xHairLines[i][1] ) + , ( xHairLines[i][2] < 0 ? width + xHairLines[i][2] : xHairLines[i][2] ) + , ( xHairLines[i][3] < 0 ? height + xHairLines[i][3] : xHairLines[i][3] ) + ); + + Line( ( xHairLines[i+1][0] < 0 ? width + xHairLines[i+1][0] : xHairLines[i+1][0] ) + , ( xHairLines[i+1][1] < 0 ? height + xHairLines[i+1][1] : xHairLines[i+1][1] ) + , ( xHairLines[i+1][2] < 0 ? width + xHairLines[i+1][2] : xHairLines[i+1][2] ) + , ( xHairLines[i+1][3] < 0 ? height + xHairLines[i+1][3] : xHairLines[i+1][3] ) + ); + End(); // update picture +} + +/* This part of code is originated from busybox library + A memory location must be free() on return + */ +static pid_t *get_pid_by_name(char *process, int *len = 0) +{ + DIR *dir; + struct dirent *next; + pid_t* pidList = NULL; + int i = 0; + char *pidName; + + pidName = process; + + dir = opendir("/proc"); + if (!dir) + printf("Cannot open /proc"); + + while ((next = readdir(dir)) != NULL) + { + FILE *status; + char filename[READ_BUF_SIZE]; + char buffer[READ_BUF_SIZE]; + char name[READ_BUF_SIZE]; + + /* Must skip ".." since that is outside /proc */ + if (strcmp(next->d_name, "..") == 0) + continue; + + /* If it isn't a number, we don't want it */ + if (!isdigit(*next->d_name)) + continue; + + sprintf(filename, "/proc/%s/status", next->d_name); + if (! (status = fopen(filename, "r")) ) + { + continue; + } + if (fgets(buffer, READ_BUF_SIZE-1, status) == NULL) + { + fclose(status); + continue; + } + fclose(status); + + /* Buffer should contain a string like "Name: binary_name" */ + sscanf(buffer, "%*s %s", name); + if (strcmp(name, pidName) == 0) + { + pidList=(pid_t *)realloc( pidList, sizeof(pid_t) * (i+2)); + pidList[i++]=(pid_t)strtol(next->d_name, NULL, 0); + } + } + if (pidList) + { + pidList[i]=0; + *len = i; + } + else + { + pidList=(pid_t *)realloc( pidList, sizeof(pid_t)); + pidList[0]=(pid_t)-1; + *len = 1; + } + return pidList; +} + +void reloadScreenConfig() { + pid_t *pid; + int i, icnt; + pid = get_pid_by_name(PROC_NAME,&icnt); + for(i=0; i<1; i++) // Send signal to only first instance of process (the parent) + { + fprintf(stdout, "SIGUSR1 to %d\n",pid[i]); + kill(pid[i],SIGUSR1); + } + free(pid); +} + +void writeConfig(const char *fname, MATRIX *config) { + FILE *stream; + char *line = NULL; + int reti; + size_t len = 0; + ssize_t nread; + + stream = fopen(fname, "w"); + if(stream != NULL) + { + fprintf(stream,"%d,%d,%d,%d,%d,%d,%d\n", + config->An, config->Bn, config->Cn, config->Dn, config->En, config->Fn,config->Divider); + } + free(line); + fclose(stream); +} + + +int main() { + int width, height, cursorx, cursory, cbsize; + + init(&width, &height); // Graphics initialization + cursorx = width / 2; + cursory = height / 2; + cbsize = (CUR_SIZ * 2) + 1; + VGImage CursorBuffer = vgCreateImage(VG_sABGR_8888, cbsize, cbsize, VG_IMAGE_QUALITY_BETTER); + + if (touchinit(width, height) != 0) { + fprintf(stderr, "Unable to initialize the touch\n"); + exit(1); + } + Start(width, height); // Start the picture + drawBackground(width,height); + + // Remove driver config, trigger reload on display + remove(FILE_COFIG); + reloadScreenConfig(); + + // Set curser values initially + // Update for screen dimensions + for(int i = 0; i width / 2) && (cursory < height / 2) ); + break; + case 4: + i = i + 2 * ( (cursorx < width / 2) && (cursory > height / 2) ); + break; + default: + i = i + 2; + } + } + // Show input values for matrix + for(int i = 0; i (%d, %d) \r\n",i, pointTouches[i].x, pointTouches[i].y, pointTargets[i].x, pointTargets[i].y); + } + setCalibrationMatrix( (POINT*)pointTargets, (POINT*)pointTouches , &calibMatrix); // update matrix + fprintf(stdout,"M: %d,%d,%d,%d,%d,%d,%d\r\n" + ,calibMatrix.An,calibMatrix.Bn,calibMatrix.Cn,calibMatrix.Dn,calibMatrix.En,calibMatrix.Fn,calibMatrix.Divider ); + + // Write out driver config file, restart display + writeConfig(FILE_COFIG, &calibMatrix); + reloadScreenConfig(); + + vgDestroyImage(CursorBuffer); // tidy up memory + finish(); // Graphics cleanup + exit(0); +} + diff --git a/util/tcTest.cpp b/util/tcTest.cpp new file mode 100644 index 0000000..64657e1 --- /dev/null +++ b/util/tcTest.cpp @@ -0,0 +1,217 @@ + + +#include +#include +#include +#include + +#include "VG/openvg.h" +#include "VG/vgu.h" +#include "fontinfo.h" +#include "shapes.h" + +#include +#include +#include + +#define xHairLines_ROWS 8 +int xHairLines[8][4] = { + 10, 15, 20, 15, // lower left + 15, 10, 15, 20, + -20, 15, -10, 15, // lower right + -15, 10, -15, 20, + 10, -15, 20, -15, // upper left + 15, -10, 15, -20, + -20, -15, -10, -15, // upper right + -15, -10, -15, -20 +}; + +// touch state structure +typedef struct { + int fd; + char* evbuff[80]; + VGfloat x, y, z; + int max_x, max_y; +} touch_t; + +touch_t touch; // global touch state +int left_count = 0; +int quitState = 0; +#define CUR_SIZ 16 // cursor size, pixels beyond centre dot + +// evenThread reads from the touch input file +void *eventThread(void *arg) { +#define OPENDEVICE_TOUCH if ((touch.fd = open("/tmp/TCfifo", O_RDONLY)) < 0) { \ + fprintf(stderr, "Error opening touch!\n"); \ + quitState = 1; \ + return &quitState;} + + // Open touch driver + OPENDEVICE_TOUCH + touch.x = touch.max_x / 2; //Reset touch + touch.y = touch.max_y / 2; + + while (1) { + uint16_t words16read[4]; + usleep(500); + if(read(touch.fd, words16read, sizeof(uint16_t) * 4 )) { + touch.x = words16read[0]; + touch.y = words16read[1]; + touch.z = words16read[2]; + close(touch.fd); + OPENDEVICE_TOUCH + } + } +} + +static int cur_sx, cur_sy, cur_w, cur_h; // cursor location and dimensions +static int cur_saved = 0; // amount of data saved in cursor image backup + +// saveCursor saves the pixels under the touch cursor +void saveCursor(VGImage CursorBuffer, int curx, int cury, int screen_width, int screen_height, int s) { + int sx, sy, ex, ey; + + sx = curx - s; // horizontal + if (sx < 0) { + sx = 0; + } + ex = curx + s; + if (ex > screen_width) { + ex = screen_width; + } + cur_sx = sx; + cur_w = ex - sx; + + sy = cury - s; // vertical + if (sy < 0) { + sy = 0; + } + ey = cury + s; + if (ey > screen_height) { + ey = screen_height; + } + cur_sy = sy; + cur_h = ey - sy; + + vgGetPixels(CursorBuffer, 0, 0, cur_sx, cur_sy, cur_w, cur_h); + cur_saved = cur_w * cur_h; +} + +// restoreCursor restores the pixels under the touch cursor +void restoreCursor(VGImage CursorBuffer) { + if (cur_saved != 0) { + vgSetPixels(cur_sx, cur_sy, CursorBuffer, 0, 0, cur_w, cur_h); + } +} + +// circleCursor draws a translucent circle as the touch cursor +void circleCursor(int curx, int cury, int width, int height, int s) { + Fill(100, 0, 0, 0.50); + Circle(curx, cury, s); + Fill(0, 0, 0, 1); + Circle(curx, cury, 2); +} + +// touchinit starts the touch event thread +int touchinit(int w, int h) { + pthread_t inputThread; + touch.max_x = w; + touch.max_y = h; + return pthread_create(&inputThread, NULL, &eventThread, NULL); +} + +void getTouches(int captures, int width, int height, int &cursorx, int &cursory, VGImage &CursorBuffer) { + int changeCount = 0; + int ztouch_thold = 50; + + do { + usleep(10000); // usec - slow loop for other threads + + // Loop until a touch is registered by a change in cursor value + if ((touch.x != cursorx || touch.y != cursory) && ((int)touch.z) > ztouch_thold ) { + restoreCursor(CursorBuffer); + cursorx = touch.x; + cursory = touch.y; + saveCursor(CursorBuffer, cursorx, cursory, width, height, CUR_SIZ); + circleCursor(cursorx, cursory, width, height, CUR_SIZ); + changeCount = changeCount + 1; + touch.z = 0.0; + End(); // update picture + } + } while (changeCount < captures); +} + +void drawBackground(int width, int height) { + Background(0, 0, 0); // Black background + Fill(44, 77, 232, 1); // Big blue marble + Circle(width / 2, 0, width); // The "world" + Fill(255, 255, 255, 1); // White text + TextMid(width / 2, height / 2, "Screen touch test", SerifTypeface, width / 15); // Greetings + End(); // update picture +} + +void drawCrosshair(int width, int height, int i) { + Stroke(255, 255, 255, 0.5); + StrokeWidth(2); + Line( ( xHairLines[i][0] < 0 ? width + xHairLines[i][0] : xHairLines[i][0] ) + , ( xHairLines[i][1] < 0 ? height + xHairLines[i][1] : xHairLines[i][1] ) + , ( xHairLines[i][2] < 0 ? width + xHairLines[i][2] : xHairLines[i][2] ) + , ( xHairLines[i][3] < 0 ? height + xHairLines[i][3] : xHairLines[i][3] ) + ); + + Line( ( xHairLines[i+1][0] < 0 ? width + xHairLines[i+1][0] : xHairLines[i+1][0] ) + , ( xHairLines[i+1][1] < 0 ? height + xHairLines[i+1][1] : xHairLines[i+1][1] ) + , ( xHairLines[i+1][2] < 0 ? width + xHairLines[i+1][2] : xHairLines[i+1][2] ) + , ( xHairLines[i+1][3] < 0 ? height + xHairLines[i+1][3] : xHairLines[i+1][3] ) + ); + End(); // update picture +} + +int main() { + int width, height, cursorx, cursory, cbsize; + + init(&width, &height); // Graphics initialization + cursorx = width / 2; + cursory = height / 2; + cbsize = (CUR_SIZ * 2) + 1; + VGImage CursorBuffer = vgCreateImage(VG_sABGR_8888, cbsize, cbsize, VG_IMAGE_QUALITY_BETTER); + + if (touchinit(width, height) != 0) { + fprintf(stderr, "Unable to initialize the touch\n"); + exit(1); + } + Start(width, height); // Start the picture + drawBackground(width,height); + + // Draw lines + for( int i = 0; i < xHairLines_ROWS - 2; ) { + drawCrosshair(width,height,i); + + getTouches(1,width,height,cursorx,cursory,CursorBuffer); + //fprintf(stdout,"p:%d %d, %d \r\n",i, cursorx,cursory); + + // Conditionally increment to next sample point + switch(i) { + case 0: + i = i + 2 * ( (cursorx < width / 2) && (cursory < height / 2) ); + break; + case 2: + i = i + 2 * ( (cursorx > width / 2) && (cursory < height / 2) ); + break; + case 4: + i = i + 2 * ( (cursorx < width / 2) && (cursory > height / 2) ); + break; + default: + i = i + 2; + } + } + + // MAIN LOOP - show some more points + getTouches(100,width,height,cursorx,cursory,CursorBuffer); + + //restoreCursor(CursorBuffer); // not strictly necessary as display will be closed + //End(); + vgDestroyImage(CursorBuffer); // tidy up memory + finish(); // Graphics cleanup + exit(0); +}