summaryrefslogtreecommitdiff
path: root/src/drivers/include
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/include')
-rw-r--r--src/drivers/include/battery.hpp16
-rw-r--r--src/drivers/include/dac.hpp73
-rw-r--r--src/drivers/include/display-init.hpp81
-rw-r--r--src/drivers/include/display.hpp67
-rw-r--r--src/drivers/include/gpio-expander.hpp208
-rw-r--r--src/drivers/include/i2c.hpp84
-rw-r--r--src/drivers/include/playback.hpp67
-rw-r--r--src/drivers/include/storage.hpp60
8 files changed, 656 insertions, 0 deletions
diff --git a/src/drivers/include/battery.hpp b/src/drivers/include/battery.hpp
new file mode 100644
index 00000000..399e866f
--- /dev/null
+++ b/src/drivers/include/battery.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "esp_err.h"
+
+namespace gay_ipod {
+
+esp_err_t init_adc(void);
+
+/**
+ * Returns the current battery level in millivolts.
+ */
+uint32_t read_battery_voltage(void);
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp
new file mode 100644
index 00000000..6d025384
--- /dev/null
+++ b/src/drivers/include/dac.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "gpio-expander.hpp"
+
+#include <stdint.h>
+#include <functional>
+
+#include "esp_err.h"
+#include "result.hpp"
+
+namespace gay_ipod {
+
+/**
+ * Interface for a PCM5122PWR DAC, configured over I2C.
+ */
+class AudioDac {
+ public:
+ enum Error {
+ FAILED_TO_BOOT,
+ FAILED_TO_CONFIGURE,
+ };
+ static auto create(GpioExpander* expander)
+ -> cpp::result<std::unique_ptr<AudioDac>, Error>;
+
+ AudioDac(GpioExpander* gpio);
+ ~AudioDac();
+
+ /**
+ * Sets the volume on a scale from 0 (loudest) to 254 (quietest). A value of
+ * 255 engages the soft mute function.
+ */
+ void WriteVolume(uint8_t volume);
+
+ enum PowerState {
+ POWERDOWN = 0b0,
+ WAIT_FOR_CP = 0b1,
+ CALIBRATION_1 = 0b10,
+ CALIBRATION_2 = 0b11,
+ RAMP_UP = 0b100,
+ RUN = 0b101,
+ SHORT = 0b110,
+ RAMP_DOWN = 0b111,
+ STANDBY = 0b1000,
+ };
+
+ /* Returns the current boot-up status and internal state of the DAC */
+ std::pair<bool, PowerState> ReadPowerState();
+
+ // Not copyable or movable.
+ AudioDac(const AudioDac&) = delete;
+ AudioDac& operator=(const AudioDac&) = delete;
+
+ private:
+ GpioExpander* gpio_;
+
+ /*
+ * Pools the power state for up to 10ms, waiting for the given predicate to
+ * be true.
+ */
+ bool WaitForPowerState(std::function<bool(bool, PowerState)> predicate);
+
+ enum Register {
+ PAGE_SELECT = 0,
+ DE_EMPHASIS = 7,
+ DIGITAL_VOLUME_L = 61,
+ DIGITAL_VOLUME_R = 62,
+ DSP_BOOT_POWER_STATE = 118,
+ };
+
+ void WriteRegister(Register reg, uint8_t val);
+};
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/display-init.hpp b/src/drivers/include/display-init.hpp
new file mode 100644
index 00000000..f11e9b57
--- /dev/null
+++ b/src/drivers/include/display-init.hpp
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <cstdint>
+
+namespace gay_ipod {
+namespace displays {
+
+extern const uint8_t kDelayBit;
+
+struct InitialisationData {
+ uint8_t num_sequences;
+ const uint8_t* sequences[4];
+};
+
+extern const InitialisationData kST7735R;
+
+/*
+ * Valid command bytes that can be sent to ST77XX displays, as well as commands
+ * for more specific variants.
+ */
+enum StCommands {
+ ST77XX_NOP = 0x00,
+ ST77XX_SWRESET = 0x01,
+ ST77XX_RDDID = 0x04,
+ ST77XX_RDDST = 0x09,
+
+ ST77XX_SLPIN = 0x10,
+ ST77XX_SLPOUT = 0x11,
+ ST77XX_PTLON = 0x12,
+ ST77XX_NORON = 0x13,
+
+ ST77XX_INVOFF = 0x20,
+ ST77XX_INVON = 0x21,
+ ST77XX_DISPOFF = 0x28,
+ ST77XX_DISPON = 0x29,
+ ST77XX_CASET = 0x2A,
+ ST77XX_RASET = 0x2B,
+ ST77XX_RAMWR = 0x2C,
+ ST77XX_RAMRD = 0x2E,
+
+ ST77XX_PTLAR = 0x30,
+ ST77XX_TEOFF = 0x34,
+ ST77XX_TEON = 0x35,
+ ST77XX_MADCTL = 0x36,
+ ST77XX_COLMOD = 0x3A,
+
+ ST77XX_MADCTL_MY = 0x80,
+ ST77XX_MADCTL_MX = 0x40,
+ ST77XX_MADCTL_MV = 0x20,
+ ST77XX_MADCTL_ML = 0x10,
+ ST77XX_MADCTL_RGB = 0x00,
+
+ ST77XX_RDID1 = 0xDA,
+ ST77XX_RDID2 = 0xDB,
+ ST77XX_RDID3 = 0xDC,
+ ST77XX_RDID4 = 0xDD,
+
+ ST7735_MADCTL_BGR = 0x08,
+ ST7735_MADCTL_MH = 0x04,
+
+ ST7735_FRMCTR1 = 0xB1,
+ ST7735_FRMCTR2 = 0xB2,
+ ST7735_FRMCTR3 = 0xB3,
+ ST7735_INVCTR = 0xB4,
+ ST7735_DISSET5 = 0xB6,
+
+ ST7735_PWCTR1 = 0xC0,
+ ST7735_PWCTR2 = 0xC1,
+ ST7735_PWCTR3 = 0xC2,
+ ST7735_PWCTR4 = 0xC3,
+ ST7735_PWCTR5 = 0xC4,
+ ST7735_VMCTR1 = 0xC5,
+
+ ST7735_PWCTR6 = 0xFC,
+
+ ST7735_GMCTRP1 = 0xE0,
+ ST7735_GMCTRN1 = 0xE1,
+};
+
+} // namespace displays
+} // namespace gay_ipod
diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp
new file mode 100644
index 00000000..2d6e9cd6
--- /dev/null
+++ b/src/drivers/include/display.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <cstdint>
+#include "display-init.hpp"
+#include "driver/spi_master.h"
+#include "gpio-expander.hpp"
+#include "lvgl/lvgl.h"
+#include "result.hpp"
+
+namespace gay_ipod {
+
+/*
+ * Display driver for LVGL.
+ */
+class Display {
+ public:
+ enum Error {};
+ static auto create(GpioExpander* expander,
+ const displays::InitialisationData& init_data)
+ -> cpp::result<std::unique_ptr<Display>, Error>;
+
+ Display(GpioExpander* gpio, spi_device_handle_t handle);
+ ~Display();
+
+ void WriteData();
+
+ void Flush(lv_disp_drv_t* disp_drv,
+ const lv_area_t* area,
+ lv_color_t* color_map);
+
+ void IRAM_ATTR PostTransaction(const spi_transaction_t& transaction);
+
+ void ServiceTransactions();
+
+ private:
+ GpioExpander* gpio_;
+ spi_device_handle_t handle_;
+
+ lv_disp_draw_buf_t buffers_;
+ lv_disp_drv_t driver_;
+ lv_disp_t* display_ = nullptr;
+
+ enum TransactionType {
+ COMMAND = 0,
+ DATA = 1,
+ };
+
+ enum TransactionFlags {
+ LVGL_FLUSH = 1,
+ };
+
+ void SendInitialisationSequence(const uint8_t* data);
+
+ void SendCommandWithData(uint8_t command,
+ const uint8_t* data,
+ size_t length,
+ uintptr_t flags = 0);
+
+ void SendCmd(const uint8_t* data, size_t length, uintptr_t flags = 0);
+ void SendData(const uint8_t* data, size_t length, uintptr_t flags = 0);
+ void SendTransaction(TransactionType type,
+ const uint8_t* data,
+ size_t length,
+ uintptr_t flags = 0);
+};
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/gpio-expander.hpp b/src/drivers/include/gpio-expander.hpp
new file mode 100644
index 00000000..2a12fba4
--- /dev/null
+++ b/src/drivers/include/gpio-expander.hpp
@@ -0,0 +1,208 @@
+#pragma once
+
+#include <stdint.h>
+#include <atomic>
+#include <functional>
+#include <mutex>
+#include <tuple>
+#include <utility>
+
+#include "driver/i2c.h"
+#include "esp_check.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+
+namespace gay_ipod {
+
+/**
+ * Wrapper for interfacing with the PCA8575 GPIO expander. Includes basic
+ * low-level pin setting methods, as well as higher level convenience functions
+ * for reading, writing, and atomically interacting with the SPI chip select
+ * pins.
+ *
+ * Each method of this class can be called safely from any thread, and all
+ * updates are guaranteed to be atomic. Any access to chip select related pins
+ * should be done whilst holding `cs_lock` (preferably via the helper methods).
+ */
+class GpioExpander {
+ public:
+ GpioExpander();
+ ~GpioExpander();
+
+ static const uint8_t kPca8575Address = 0x20;
+ static const uint8_t kPca8575Timeout = 100 / portTICK_RATE_MS;
+
+ // Port A:
+ // 0 - audio power enable
+ // 1 - usb interface power enable
+ // 2 - display power enable
+ // 3 - sd card power enable
+ // 4 - charge power ok (active low)
+ // 5 - sd mux switch
+ // 6 - sd chip select
+ // 7 - display chip select
+ // All power switches low, chip selects high, active-low charge power high
+ static const uint8_t kPortADefault = 0b11010001;
+
+ // Port B:
+ // 0 - 3.5mm jack detect (active low)
+ // 1 - dac soft mute switch
+ // 2 - GPIO
+ // 3 - GPIO
+ // 4 - GPIO
+ // 5 - GPIO
+ // 6 - GPIO
+ // 7 - GPIO
+ // DAC mute output low, everything else is active-low inputs.
+ static const uint8_t kPortBDefault = 0b11111111;
+
+ /*
+ * Convenience mehod for packing the port a and b bytes into a single 16 bit
+ * value.
+ */
+ static uint16_t pack(uint8_t a, uint8_t b) { return ((uint16_t)b) << 8 | a; }
+
+ /*
+ * Convenience mehod for unpacking the result of `pack` back into two single
+ * byte port datas.
+ */
+ static std::pair<uint8_t, uint8_t> unpack(uint16_t ba) {
+ return std::pair((uint8_t)ba, (uint8_t)(ba >> 8));
+ }
+
+ /*
+ * Convenience function for running some arbitrary pin writing code, then
+ * flushing a `Write()` to the expander. Example usage:
+ *
+ * ```
+ * gpio_.with([&](auto& gpio) {
+ * gpio.set_pin(AUDIO_POWER_ENABLE, true);
+ * });
+ * ```
+ */
+ void with(std::function<void(GpioExpander&)> f);
+
+ /**
+ * Sets the ports on the GPIO expander to the values currently represented
+ * in `ports`.
+ */
+ esp_err_t Write(void);
+
+ /**
+ * Reads from the GPIO expander, populating `inputs` with the most recent
+ * values.
+ */
+ esp_err_t Read(void);
+
+ /* Maps each pin of the expander to its number in a `pack`ed uint16. */
+ enum Pin {
+ // Port A
+ AUDIO_POWER_ENABLE = 0,
+ USB_INTERFACE_POWER_ENABLE = 1,
+ DISPLAY_POWER_ENABLE = 2,
+ SD_CARD_POWER_ENABLE = 3,
+ CHARGE_POWER_OK = 4, // Active-low input
+ SD_MUX_SWITCH = 5,
+ SD_CHIP_SELECT = 6,
+ DISPLAY_CHIP_SELECT = 7,
+
+ // Port B
+ PHONE_DETECT = 8, // Active-high input
+ DAC_MUTE = 9,
+ GPIO_1 = 10,
+ GPIO_2 = 11,
+ GPIO_3 = 12,
+ GPIO_4 = 13,
+ GPIO_5 = 14,
+ GPIO_6 = 15,
+ };
+
+ /* Pins whose access should be guarded by `cs_lock`. */
+ enum ChipSelect {
+ SD_CARD = SD_CHIP_SELECT,
+ DISPLAY = DISPLAY_CHIP_SELECT,
+ };
+
+ /* Nicer value names for use with the SD_MUX_SWITCH pin. */
+ enum SdController {
+ SD_MUX_ESP = 0,
+ SD_MUX_USB = 1,
+ };
+
+ /**
+ * Returns the current driven status of each of the ports. The first byte is
+ * port a, and the second byte is port b.
+ */
+ std::atomic<uint16_t>& ports() { return ports_; }
+
+ /*
+ * Sets a single specific pin to the given value. `true` corresponds to
+ * HIGH, and `false` corresponds to LOW.
+ *
+ * Calls to this method will be buffered in memory until a call to `Write()`
+ * is made.
+ */
+ void set_pin(Pin pin, bool value);
+ void set_pin(ChipSelect cs, bool value);
+
+ /**
+ * Returns the input status of each of the ports. The first byte is port a,
+ * and the second byte is port b.
+ */
+ const std::atomic<uint16_t>& inputs() const { return inputs_; }
+
+ /* Returns the most recently cached value of the given pin. Only valid for
+ * pins used as inputs; to check what value we're driving a pin, use
+ * `ports()`.
+ */
+ bool get_input(Pin pin) const;
+
+ /* Returns the mutex that must be held whilst pulling a CS pin low. */
+ std::mutex& cs_mutex() { return cs_mutex_; }
+
+ /*
+ * Helper class containing an active `cs_mutex` lock. When an instance of
+ * this class is destroyed (usually by falling out of scope), the associated
+ * CS pin will be driven high before the lock is released.
+ */
+ class SpiLock {
+ public:
+ SpiLock(GpioExpander& gpio, ChipSelect cs);
+ ~SpiLock();
+
+ SpiLock(const SpiLock&) = delete;
+
+ private:
+ std::scoped_lock<std::mutex> lock_;
+ GpioExpander& gpio_;
+ ChipSelect cs_;
+ };
+
+ /*
+ * Pulls the given CS pin low to signal that we are about to communicate
+ * with a particular device, after acquiring a lock on `cs_mutex`. The
+ * recommended way to safely interact with devices on the SPI bus is to have
+ * a self-contained block like so:
+ *
+ * ```
+ * {
+ * auto lock = AcquireSpiBus(WHATEVER);
+ * // Do some cool things here.
+ * }
+ * ```
+ */
+ SpiLock AcquireSpiBus(ChipSelect cs);
+
+ // Not copyable or movable. There should usually only ever be once instance
+ // of this class, and that instance will likely have a static lifetime.
+ GpioExpander(const GpioExpander&) = delete;
+ GpioExpander& operator=(const GpioExpander&) = delete;
+
+ private:
+ std::mutex cs_mutex_;
+ std::atomic<uint16_t> ports_;
+ std::atomic<uint16_t> inputs_;
+};
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/i2c.hpp b/src/drivers/include/i2c.hpp
new file mode 100644
index 00000000..db554f5d
--- /dev/null
+++ b/src/drivers/include/i2c.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <cstdint>
+
+#include "driver/i2c.h"
+#include "hal/i2c_types.h"
+
+namespace gay_ipod {
+
+/*
+ * Convenience wrapper for performing an I2C transaction with a reasonable
+ * preconfigured timeout, automatic management of a heap-based command buffer,
+ * and a terser API for enqueuing bytes.
+ *
+ * Any error codes from the underlying ESP IDF are treated as fatal, since they
+ * typically represent invalid arguments or OOMs.
+ */
+class I2CTransaction {
+ public:
+ static const uint8_t kI2CTimeout = 100 / portTICK_RATE_MS;
+
+ I2CTransaction();
+ ~I2CTransaction();
+
+ /*
+ * Executes all enqueued commands, returning the result code. Possible error
+ * codes, per the ESP-IDF docs:
+ *
+ * ESP_OK Success
+ * ESP_ERR_INVALID_ARG Parameter error
+ * ESP_FAIL Sending command error, slave doesn’t ACK the transfer.
+ * ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
+ * ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
+ */
+ esp_err_t Execute();
+
+ /*
+ * Enqueues a start condition. May also be used for repeated start
+ * conditions.
+ */
+ I2CTransaction& start();
+ /* Enqueues a stop condition. */
+ I2CTransaction& stop();
+
+ /*
+ * Enqueues writing the given 7 bit address, followed by one bit indicating
+ * whether this is a read or write request.
+ *
+ * This command will expect an ACK before continuing.
+ */
+ I2CTransaction& write_addr(uint8_t addr, uint8_t op);
+
+ /*
+ * Enqueues one or more bytes to be written. The transaction will wait for
+ * an ACK to be returned before writing the next byte.
+ */
+ I2CTransaction& write_ack(uint8_t data);
+ template <typename... More>
+ I2CTransaction& write_ack(uint8_t data, More... more) {
+ write_ack(data);
+ write_ack(more...);
+ return *this;
+ }
+
+ /*
+ * Enqueues a read of one byte into the given uint8. Responds with the given
+ * ACK/NACK type.
+ */
+ I2CTransaction& read(uint8_t* dest, i2c_ack_type_t ack);
+
+ /* Returns the underlying command buffer. */
+ i2c_cmd_handle_t handle() { return handle_; }
+
+ // Cannot be moved or copied, since doing so is probably an error. Pass a
+ // reference instead.
+ I2CTransaction(const I2CTransaction&) = delete;
+ I2CTransaction& operator=(const I2CTransaction&) = delete;
+
+ private:
+ i2c_cmd_handle_t handle_;
+ uint8_t* buffer_;
+};
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/playback.hpp b/src/drivers/include/playback.hpp
new file mode 100644
index 00000000..493dd311
--- /dev/null
+++ b/src/drivers/include/playback.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "dac.hpp"
+#include "storage.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include "audio_event_iface.h"
+#include "audio_pipeline.h"
+#include "esp_err.h"
+#include "fatfs_stream.h"
+#include "i2s_stream.h"
+#include "mp3_decoder.h"
+#include "result.hpp"
+
+namespace gay_ipod {
+
+class DacAudioPlayback {
+ public:
+ enum Error { PIPELINE_INIT };
+ static auto create(AudioDac* dac)
+ -> cpp::result<std::unique_ptr<DacAudioPlayback>, Error>;
+
+ DacAudioPlayback(AudioDac* dac,
+ audio_pipeline_handle_t pipeline,
+ audio_element_handle_t fatfs_stream_reader,
+ audio_element_handle_t i2s_stream_writer,
+ audio_event_iface_handle_t event_interface,
+ audio_element_handle_t mp3_decoder);
+ ~DacAudioPlayback();
+
+ void Play(const std::string& filename);
+ void Resume();
+ void Pause();
+
+ void ProcessEvents();
+
+ /* for gapless */
+ void set_next_file(const std::string& filename);
+
+ void set_volume(uint8_t volume);
+ auto volume() -> uint8_t;
+
+ // Not copyable or movable.
+ DacAudioPlayback(const DacAudioPlayback&) = delete;
+ DacAudioPlayback& operator=(const DacAudioPlayback&) = delete;
+
+ private:
+ AudioDac* dac_;
+ std::mutex playback_lock_;
+
+ std::string next_filename_;
+ uint8_t volume_;
+
+ audio_pipeline_handle_t pipeline_;
+ audio_element_handle_t fatfs_stream_reader_;
+ audio_element_handle_t i2s_stream_writer_;
+ audio_event_iface_handle_t event_interface_;
+
+ audio_element_handle_t mp3_decoder_;
+};
+
+} // namespace gay_ipod
diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp
new file mode 100644
index 00000000..cee49cc5
--- /dev/null
+++ b/src/drivers/include/storage.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "gpio-expander.hpp"
+
+#include <memory>
+
+#include "driver/sdmmc_types.h"
+#include "driver/sdspi_host.h"
+#include "esp_err.h"
+#include "esp_vfs_fat.h"
+#include "result.hpp"
+
+namespace gay_ipod {
+
+extern const char* kStoragePath;
+
+class SdStorage {
+ public:
+ enum Error {
+ FAILED_TO_INIT,
+ /** We couldn't interact with the SD card at all. Is it missing? */
+ FAILED_TO_READ,
+ /** We couldn't mount the SD card. Is it formatted? */
+ FAILED_TO_MOUNT,
+ };
+
+ static auto create(GpioExpander* gpio)
+ -> cpp::result<std::unique_ptr<SdStorage>, Error>;
+
+ SdStorage(GpioExpander* gpio,
+ esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*),
+ sdspi_dev_handle_t handle_,
+ std::unique_ptr<sdmmc_host_t>& host_,
+ std::unique_ptr<sdmmc_card_t>& card_,
+ FATFS* fs_);
+ ~SdStorage();
+
+ auto HandleTransaction(sdspi_dev_handle_t handle, sdmmc_command_t* cmdinfo)
+ -> esp_err_t;
+
+ // Not copyable or movable.
+ // TODO: maybe this could be movable?
+ SdStorage(const SdStorage&) = delete;
+ SdStorage& operator=(const SdStorage&) = delete;
+
+ private:
+ GpioExpander* gpio_;
+
+ esp_err_t (*do_transaction_)(sdspi_dev_handle_t, sdmmc_command_t*) = nullptr;
+
+ // SPI and SD driver info
+ sdspi_dev_handle_t handle_;
+ std::unique_ptr<sdmmc_host_t> host_;
+ std::unique_ptr<sdmmc_card_t> card_;
+
+ // Filesystem info
+ FATFS* fs_ = nullptr;
+};
+
+} // namespace gay_ipod