/* * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. * Copyleft (ɔ) 2024 zxkmm under GPL license * Copyright (C) 2024 u-foka * Copyright (C) 2024 Mark Thompson * * This file is part of PortaPack. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef __PORTAPACK_IO_H__ #define __PORTAPACK_IO_H__ #include #include #include #include "platform.hpp" #include "gpio.hpp" #include "ui.hpp" // #include "portapack_persistent_memory.hpp" // Darkened pixel bit mask for each possible shift value. static const uint16_t darken_mask[4] = { 0b1111111111111111, // RrrrrGgggggBbbbb 0b0111101111101111, // 0Rrrr0Ggggg0Bbbb 0b0011100111100111, // 00Rrr00Gggg00Bbb 0b0001100011100011 // 000Rr000Ggg000Bb }; // To darken, dividing each color level R/G/B by 2^shift. #define DARKENED_PIXEL(pixel, shift) ((pixel >> shift) & darken_mask[shift]) // To un-darken, multiply each color level by 2^shift (might still be darker that before since some bits may have been lost above). // This function will only be called when the pixel has previously been darkened, so no masking is needed. #define UNDARKENED_PIXEL(pixel, shift) (pixel << shift) namespace portapack { class IO { public: enum class TouchPinsConfig : uint8_t { XN_BIT = (1 << 0), XP_BIT = (1 << 1), YN_BIT = (1 << 2), YP_BIT = (1 << 3), XN_OE = (1 << 4), XP_OE = (1 << 5), YN_OE = (1 << 6), YP_OE = (1 << 7), XN_IN = XN_BIT, XN_OUT_1 = XN_OE | XN_BIT, XN_OUT_0 = XN_OE, XP_IN = XP_BIT, XP_OUT_1 = XP_OE | XP_BIT, XP_OUT_0 = XP_OE, YN_IN = YN_BIT, YN_OUT_1 = YN_OE | YN_BIT, YN_OUT_0 = YN_OE, YP_IN = YP_BIT, YP_OUT_1 = YP_OE | YP_BIT, YP_OUT_0 = YP_OE, /* Allow pins to be pulled up by CPLD pull-ups. */ Float = XP_IN | XN_IN | YP_IN | YN_IN, /* Drive one plane to 0V, other plane is pulled up. Watch for when pulled-up * plane falls to ~0V. */ WaitTouch = XP_OUT_0 | XN_OUT_0 | YP_IN | YN_IN, /* Create a voltage divider between X plane, touch resistance, Y plane. */ SensePressure = XP_IN | XN_OUT_0 | YP_OUT_1 | YN_IN, /* Create a voltage divider across X plane, read voltage from Y plane. */ SenseX = XP_OUT_1 | XN_OUT_0 | YP_IN | YN_IN, /* Create a voltage divider across Y plane, read voltage from X plane. */ SenseY = XP_IN | XN_IN | YP_OUT_1 | YN_OUT_0, }; constexpr IO( GPIO gpio_dir, GPIO gpio_lcd_rdx, GPIO gpio_lcd_wrx, GPIO gpio_io_stbx, GPIO gpio_addr, GPIO gpio_rot_a, GPIO gpio_rot_b) : gpio_dir{gpio_dir}, gpio_lcd_rdx{gpio_lcd_rdx}, gpio_lcd_wrx{gpio_lcd_wrx}, gpio_io_stbx{gpio_io_stbx}, gpio_addr{gpio_addr}, gpio_rot_a{gpio_rot_a}, gpio_rot_b{gpio_rot_b} {}; void init(); void lcd_backlight(const bool value); void lcd_reset_state(const bool active); void audio_reset_state(const bool active); void reference_oscillator(const bool enable); void lcd_data_write_command_and_data( const uint_fast8_t command, const uint8_t* data, const size_t data_count) { lcd_command(command); for (size_t i = 0; i < data_count; i++) { lcd_write_data(data[i]); } } void lcd_data_write_command_and_data( const uint_fast8_t command, const std::initializer_list& data) { lcd_command(command); for (const auto d : data) { lcd_write_data(d); } } void lcd_data_read_command_and_data( const uint_fast8_t command, uint16_t* const data, const size_t data_count) { lcd_command(command); for (size_t i = 0; i < data_count; i++) { data[i] = lcd_read_data(); } } void lcd_write_word(const uint32_t w) { lcd_write_data(w); } void lcd_write_words(const uint16_t* const w, size_t n) { for (size_t i = 0; i < n; i++) { lcd_write_data(w[i]); } } void lcd_write_pixel(ui::Color pixel) { lcd_write_data(pixel.v); } uint32_t lcd_read_word() { return lcd_read_data(); } void lcd_write_pixels(ui::Color pixel, size_t n) { while (n--) { lcd_write_data(pixel.v); } } void lcd_write_pixels_unrolled8(ui::Color pixel, size_t n) { auto v = pixel.v; n >>= 3; while (n--) { lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); lcd_write_data(v); } } void lcd_write_pixels(const ui::Color* const pixels, size_t n) { for (size_t i = 0; i < n; i++) { lcd_write_pixel(pixels[i]); } } void lcd_read_bytes(uint8_t* byte, size_t byte_count) { size_t word_count = byte_count / 2; while (word_count) { const auto word = lcd_read_data(); *(byte++) = word >> 8; *(byte++) = word >> 0; word_count--; } if (byte_count & 1) { const auto word = lcd_read_data(); *(byte++) = word >> 8; } } uint32_t io_read() { io_stb_assert(); dir_read(); addr_0(); __asm__("nop"); __asm__("nop"); __asm__("nop"); const auto switches_raw = data_read(); io_stb_deassert(); return switches_raw; } bool get_is_inverted(); uint32_t io_update(const TouchPinsConfig write_value); uint32_t lcd_te() { return gpio_rot_a.read(); } uint32_t dfu_read() { return gpio_rot_b.read(); } private: const GPIO gpio_dir; const GPIO gpio_lcd_rdx; const GPIO gpio_lcd_wrx; const GPIO gpio_io_stbx; const GPIO gpio_addr; const GPIO gpio_rot_a; const GPIO gpio_rot_b; static constexpr ioportid_t gpio_data_port_id = 3; static constexpr size_t gpio_data_shift = 8; static constexpr ioportmask_t gpio_data_mask = 0xffU << gpio_data_shift; uint8_t io_reg{0x03}; void lcd_rd_assert() { gpio_lcd_rdx.clear(); } void lcd_rd_deassert() { gpio_lcd_rdx.set(); } void lcd_wr_assert() { gpio_lcd_wrx.clear(); } void lcd_wr_deassert() { gpio_lcd_wrx.set(); } void io_stb_assert() { gpio_io_stbx.clear(); } void io_stb_deassert() { gpio_io_stbx.set(); } void addr(const bool value) { gpio_addr.write(value); } void addr_1() { gpio_addr.set(); } void addr_0() { gpio_addr.clear(); } void data_mask_set() { LPC_GPIO->MASK[gpio_data_port_id] = ~gpio_data_mask; } void dir_write() { gpio_dir.clear(); LPC_GPIO->DIR[gpio_data_port_id] |= gpio_data_mask; /* TODO: Manipulating DIR[3] makes me queasy. The RFFC5072 DATA pin * is also on port 3, and switches direction periodically... * Time to resort to bit-banding to enforce atomicity? But then, how * to change direction on eight bits efficiently? Or do I care, since * the PortaPack data bus shouldn't change direction too frequently? */ } void dir_read() { LPC_GPIO->DIR[gpio_data_port_id] &= ~gpio_data_mask; gpio_dir.set(); } void data_write_low(const uint32_t value) { LPC_GPIO->MPIN[gpio_data_port_id] = (value << gpio_data_shift); } void data_write_high(const uint32_t value) { LPC_GPIO->MPIN[gpio_data_port_id] = value; } uint32_t data_read() { return (LPC_GPIO->MPIN[gpio_data_port_id] >> gpio_data_shift) & 0xffU; } void lcd_command(const uint32_t value) { data_write_high(0); /* Drive high byte (with zero -- don't care) */ dir_write(); /* Turn around data bus, MCU->CPLD */ addr(0); /* Indicate command */ __asm__("nop"); __asm__("nop"); __asm__("nop"); lcd_wr_assert(); /* Latch high byte */ data_write_low(value); /* Drive low byte (pass-through) */ __asm__("nop"); __asm__("nop"); __asm__("nop"); lcd_wr_deassert(); /* Complete write operation */ addr(1); /* Set up for data phase (most likely after a command) */ } // void high_contrast(ui::Color& pixel, size_t contrast_level_shift) { // TODO // uint16_t r = (pixel.v >> 11) & 0x1F; // uint16_t g = (pixel.v >> 5) & 0x3F; // uint16_t b = pixel.v & 0x1F; // // if ((r << contrast_level_shift) > 0x1F) { // should be slightly smaller, need more obverse... // r = 0x1F; // } else { // r = r << contrast_level_shift; // } // // if ((g << contrast_level_shift) > 0x3F) { // same as above // g = 0x3F; // } else { // g = g << contrast_level_shift; // } // // if ((b << contrast_level_shift) > 0x1F) { // same as above // b = 0x1F; // } else { // b = b << contrast_level_shift; // } // // pixel.v = (r << 11) | (g << 5) | b; // } // // void gray_scale(ui::Color& pixel) { // TODO: the blackwhite not looks right.... // uint16_t r = (pixel.v >> 11) & 0x1F; // uint16_t g = (pixel.v >> 5) & 0x3F; // uint16_t b = pixel.v & 0x1F; // // uint16_t average = (r + g + b) / 3; // // pixel.v = (average << 11) | (average << 5) | average; // } void lcd_write_data(const uint32_t value) __attribute__((always_inline)) { // NOTE: Assumes and DIR=0 and ADDR=1 from command phase. data_write_high(value); /* Drive high byte */ __asm__("nop"); lcd_wr_assert(); /* Latch high byte */ data_write_low(value); /* Drive low byte (pass-through) */ __asm__("nop"); __asm__("nop"); __asm__("nop"); lcd_wr_deassert(); /* Complete write operation */ } uint32_t lcd_read_data() { // NOTE: Assumes ADDR=1 from command phase. dir_read(); /* Start read operation */ lcd_rd_assert(); /* Wait for passthrough data(15:8) to settle -- ~16ns (3 cycles) typical */ /* Wait for read control L duration (355ns) */ halPolledDelay(71); // 355ns const auto value_high = data_read(); /* Latch data[7:0] */ lcd_rd_deassert(); /* Wait for latched data[7:0] to settle -- ~26ns (5 cycles) typical */ /* Wait for read control H duration (90ns) */ halPolledDelay(18); // 90ns const auto value_low = data_read(); uint32_t original_value = (value_high << 8) | value_low; return original_value; } void io_write(const bool address, const uint_fast16_t value) { data_write_low(value); dir_write(); addr(address); __asm__("nop"); __asm__("nop"); __asm__("nop"); io_stb_assert(); __asm__("nop"); __asm__("nop"); __asm__("nop"); io_stb_deassert(); } /* void lcd_data_write_command_and_data( const uint_fast16_t command, const uint8_t* const data, const size_t count ) { lcd_data_write_command(command); for(size_t i=0; i