| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- // SPDX-License-Identifier: BSD-2-Clause
- // Copyright (c) 2023 KBEmbedded
- #include <furi.h>
- #include <furi_hal.h>
- #include <stm32wbxx_ll_exti.h>
- #include <stm32wbxx_ll_system.h>
- #include <stdint.h>
- #include <gblink/include/gblink.h>
- #include "exti_workaround_i.h"
- #include "clock_timer_i.h"
- struct gblink_pins {
- const GpioPin *serin;
- const GpioPin *serout;
- const GpioPin *clk;
- const GpioPin *sd;
- };
- const struct gblink_pins common_pinouts[PINOUT_COUNT] = {
- /* Original */
- {
- &gpio_ext_pc3,
- &gpio_ext_pb3,
- &gpio_ext_pb2,
- &gpio_ext_pa4,
- },
- /* MALVEKE EXT1 */
- {
- &gpio_ext_pa6,
- &gpio_ext_pa7,
- &gpio_ext_pb3,
- &gpio_ext_pa4,
- },
- };
- struct gblink {
- const GpioPin *serin;
- const GpioPin *serout;
- const GpioPin *clk;
- const GpioPin *sd;
- gblink_mode mode;
- void (*callback)(void* cb_context, uint8_t in);
- void *cb_context;
- /* These two semaphores serve similar but distinct purposes. */
- /* The transfer semaphore is taken as soon as a transfer() request
- * has been started. This is used in the function to wait until the
- * transfer has been completed.
- */
- FuriSemaphore *transfer_sem;
- /* The out byte semaphore is used to indicate that a byte transfer
- * is in progress. This is used in the transfer function to not allow
- * a transfer request if we're in the middle of sending a byte.
- * The transfer semaphore is not used for that purpose since if the
- * Flipper is in EXT clk mode, once a transfer() is started, there
- * would be no way to both prevent transfer() from being called again
- * as well as cancelling/changing what we're wanting to send. Using
- * out byte semaphore means a transfer() can be called at any time,
- * waited on synchronously for a timeout, and then re-called at a
- * later time; while blocking that update if a byte is actually
- * in the middle of being transmitted.
- */
- FuriSemaphore *out_byte_sem;
- /* Used to lock out changing things after a certain point. Pinout,
- * mode, etc.
- * XXX: Might make more sense to use the mutex to protect a flag?
- * Maybe a semaphore? Though I think that is the wrong use.
- */
- FuriMutex *start_mutex;
- /*
- * The following should probably have the world stopped around them
- * if not modified in an interrupt context.
- */
- uint8_t in;
- uint8_t out;
- uint8_t shift;
- uint8_t nobyte;
- /* Should only be changed when not in middle of tx, will affect a lot */
- gblink_clk_source source;
- /* Can be changed at any time, will only take effect on the next
- * transfer.
- */
- gblink_speed speed;
- /*
- * The following is based on observing Pokemon trade data
- *
- * Clocks idle between bytes is nominally 430 us long for burst data,
- * 15 ms for idle polling (e.g. waiting for menu selection), some oddball
- * 2 ms gaps that appears between one 0xFE byte from the Game Boy every trade;
- * clock period is nominally 122 us.
- *
- * Therefore, if we haven't seen a clock in 500 us, reset our bit counter.
- * Note that, this should never actually be a concern, but it is an additional
- * safeguard against desyncing.
- */
- uint32_t time;
- uint32_t bitclk_timeout_us;
- void *exti_workaround_handle;
- };
- static inline bool gblink_transfer_in_progress(struct gblink *gblink)
- {
- return !(furi_semaphore_get_count(gblink->out_byte_sem));
- }
- /* XXX: TODO: Investigate how exceeding the timeout would work if in INT clock
- * mode. I think this would reset the state machine, but, I don't think the
- * transfer would be restarted with the correct data.
- */
- static void gblink_shift_in_isr(struct gblink *gblink)
- {
- const uint32_t time_ticks = furi_hal_cortex_instructions_per_microsecond() * gblink->bitclk_timeout_us;
- if (gblink->source == GBLINK_CLK_INT)
- furi_hal_gpio_write(gblink->clk, 1);
- /* If we exceeded the bit clock timeout, reset all counters */
- if ((DWT->CYCCNT - gblink->time) > time_ticks) {
- gblink->in = 0;
- gblink->shift = 0;
- }
- gblink->time = DWT->CYCCNT;
- gblink->in <<= 1;
- gblink->in |= furi_hal_gpio_read(gblink->serin);
- gblink->shift++;
- /* If 8 bits transfered, reset shift counter, call registered
- * callback, re-set nobyte in output buffer.
- */
- if (gblink->shift == 8) {
- if (gblink->source == GBLINK_CLK_INT)
- clock_timer_stop();
- gblink->shift = 0;
- /*
- * Set up next out byte before calling the callback.
- * This is in case the callback itself sets a new out
- * byte which it will in most cases.
- *
- * The nobyte value is set in place as the next output byte,
- * in case the flipper does not set a real byte before the next
- * transfer starts.
- */
- gblink->out = gblink->nobyte;
- furi_semaphore_release(gblink->out_byte_sem);
- /*
- * Call the callback, if set, and then release the semaphore
- * in case a thread is waiting on TX to complete.
- */
- if (gblink->callback)
- gblink->callback(gblink->cb_context, gblink->in);
- furi_semaphore_release(gblink->transfer_sem);
- }
- }
- static void gblink_shift_out_isr(struct gblink *gblink)
- {
- furi_semaphore_acquire(gblink->out_byte_sem, 0);
- furi_hal_gpio_write(gblink->serout, !!(gblink->out & 0x80));
- gblink->out <<= 1;
- /* XXX: TODO: Check that this is the correct thing with open drain.
- * does 0 value actually drive the line low, or high?
- */
- if (gblink->source == GBLINK_CLK_INT)
- furi_hal_gpio_write(gblink->clk, 0);
- }
- static void gblink_clk_isr(void *context)
- {
- furi_assert(context);
- struct gblink *gblink = context;
- bool out = false;
- /*
- * Whether we're shifting in or out is dependent on the clock source.
- * If external, and the clock line is high, that means a posedge just
- * occurred and we need to shift data in.
- *
- * If internal, and the clock line is high, that means we're about
- * to drive a negedge and need to shift data out.
- *
- * The actual in/out functions drive the clock state at the right times
- * if the clock is internal source.
- */
- out = (furi_hal_gpio_read(gblink->clk) ==
- (gblink->source == GBLINK_CLK_INT));
- if (out)
- gblink_shift_out_isr(gblink);
- else
- gblink_shift_in_isr(gblink);
- }
- /*
- * Call to set up the clk pin modes to do the right thing based on if INT or
- * EXT clock source is configured.
- */
- static void gblink_clk_configure(struct gblink *gblink)
- {
- if (gblink->source == GBLINK_CLK_EXT) {
- furi_hal_gpio_init(gblink->clk, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
- /* furi_hal_gpio_init, while it sets interrupt settings on the GPIO,
- * does not actually enable the EXTI interrupt.
- */
- gblink_int_enable(gblink);
- } else {
- /* This will disable the EXTI interrupt for us */
- furi_hal_gpio_init(gblink->clk, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh);
- }
- }
- void gblink_clk_source_set(void *handle, gblink_clk_source source)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (source == gblink->source)
- return;
-
- /*
- * NOTE:
- * I'm not sure the best way to handle this at the moment. In theory,
- * it should be safe to check that we're just not in the middle of a
- * transfer and not worry about getting stuck.
- * However, I'm not really sure how true that is, so for now this will
- * always change the source and reset the current byte transfer.
- * It is up to the callee to ensure that they are between bytes.
- *
- * One idea would be to get the semaphore, but wait the set timeout.
- * if that is exceeded or the semaphore is acquired, then its probably
- * safe to change the source and reset shift register.
- */
- gblink->source = source;
- gblink->shift = 0;
- gblink_clk_configure(gblink);
- }
- void gblink_speed_set(void *handle, gblink_speed speed)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /*
- * This does not need any protection, it will take effect at the start
- * of the next byte.
- */
- gblink->speed = speed;
- }
- /* default is set to 500 us */
- void gblink_timeout_set(void *handle, uint32_t us)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- gblink->bitclk_timeout_us = us;
- }
- int gblink_pin_set(void *handle, gblink_bus_pins pin, const GpioPin *gpio)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (furi_mutex_acquire(gblink->start_mutex, 0) != FuriStatusOk)
- return 1;
- switch (pin) {
- case PIN_SERIN:
- gblink->serin = gpio;
- break;
- case PIN_SEROUT:
- gblink->serout = gpio;
- break;
- case PIN_CLK:
- gblink->clk = gpio;
- break;
- case PIN_SD:
- gblink->sd = gpio;
- break;
- default:
- furi_crash();
- break;
- }
- furi_mutex_release(gblink->start_mutex);
- return 0;
- }
- int gblink_pin_set_default(void *handle, gblink_pinouts pinout)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (furi_mutex_acquire(gblink->start_mutex, 0) != FuriStatusOk)
- return 1;
- gblink->serin = common_pinouts[pinout].serin;
- gblink->serout = common_pinouts[pinout].serout;
- gblink->clk = common_pinouts[pinout].clk;
- gblink->sd = common_pinouts[pinout].sd;
- furi_mutex_release(gblink->start_mutex);
- return 0;
- }
- int gblink_pin_get_default(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- int i;
- for (i = 0; i < PINOUT_COUNT; i++) {
- if (gblink->serin != common_pinouts[i].serin)
- continue;
- if (gblink->serout != common_pinouts[i].serout)
- continue;
- if (gblink->clk != common_pinouts[i].clk)
- continue;
- /* XXX: Currently not checked or used! */
- //if (gblink->sd != common_pinouts[pinout].sd;
- break;
- }
- if (i == PINOUT_COUNT)
- i = -1;
- return i;
- }
- const GpioPin *gblink_pin_get(void *handle, gblink_bus_pins pin)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- switch (pin) {
- case PIN_SERIN:
- return gblink->serin;
- case PIN_SEROUT:
- return gblink->serout;
- case PIN_CLK:
- return gblink->clk;
- case PIN_SD:
- return gblink->sd;
- default:
- furi_crash();
- break;
- }
- return NULL;
- }
- int gblink_callback_set(void *handle, void (*callback)(void* cb_context, uint8_t in), void *cb_context)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (furi_mutex_acquire(gblink->start_mutex, 0) != FuriStatusOk)
- return 1;
- gblink->callback = callback;
- gblink->cb_context = cb_context;
- furi_mutex_release(gblink->start_mutex);
- return 0;
- }
- int gblink_mode_set(void *handle, gblink_mode mode)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- if (furi_mutex_acquire(gblink->start_mutex, 0) != FuriStatusOk)
- return 1;
- gblink->mode = mode;
- furi_mutex_release(gblink->start_mutex);
- return 0;
- }
- /* XXX: TODO: This doesn't check for start! */
- bool gblink_transfer(void *handle, uint8_t val)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- bool ret = false;
- /* Stop the world, this is to ensure we can safely set the next out byte */
- /*
- * The reason for and therefore issue of setting the next byte has a few
- * points to keep in mind.
- *
- * First, with EXT clock source, the first hint of the external device
- * clocking in data is a negative edge where it would set data. This
- * means that the next out byte needs to be set before that.
- *
- * Second, since the interrupt on the neg clock edge loads the next
- * byte in to serout after grabbing the semaphore; we can stop the
- * world right now, and set the byte if there is no transfer in
- * progress. As soon as the world is resumed, the IRQ will fire, and
- * the correct, new, data byte will start to be shifted out.
- */
- FURI_CRITICAL_ENTER();
- /* If we're in the middle of a tranfer, don't let the byte be set. */
- if (!gblink_transfer_in_progress(gblink)) {
- gblink->out = val;
- ret = true;
- /*
- * Now that we're this far, this means the byte we set will be
- * transferred one way or another. Because of that, take the
- * transfer semaphore. This gets released once a full byte has
- * been transferred. This is for the TX wait function. We cannot
- * use the out_byte_sem as if the wait is called immediately
- * after the transfer, and no data has yet been shifted out,
- * the TX wait function would incorrectly return immediately.
- */
- furi_semaphore_acquire(gblink->transfer_sem, 0);
- }
- FURI_CRITICAL_EXIT();
- /*
- * If the out byte was successfully set, and we're driving the clock,
- * turn on our timer for byte transfer.
- */
- if (ret && gblink->source == GBLINK_CLK_INT)
- clock_timer_start(gblink_clk_isr, gblink, gblink->speed);
- return ret;
- }
- uint8_t gblink_transfer_tx_wait_complete(void *handle)
- {
- struct gblink *gblink = handle;
- /* XXX: TODO: Think about how to implement this in a way that we can
- * use the semaphore to see if there is a transfer waiting to happen,
- * but not in a way that would incorrectly show a transfer waiting. e.g.
- * if this takes the semaphore, then the semaphore is in the same state
- * as if a transfer was in progress. Should this put back the semaphore
- * after acquiring it? Is there a better way of handling it?
- */
- furi_semaphore_acquire(gblink->transfer_sem, FuriWaitForever);
- return gblink->in;
- }
- void gblink_nobyte_set(void *handle, uint8_t val)
- {
- struct gblink *gblink = handle;
- /*
- * This is safe to run at any time. It is only copied in after a byte
- * transfer is completed.
- */
- gblink->nobyte = val;
- }
- void gblink_int_enable(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /*
- * NOTE: This is currently safe to run even with the exti workaround
- * in effect. It just enables the root EXTI interrupt source of the
- * given pin.
- */
- furi_hal_gpio_enable_int_callback(gblink->clk);
- }
- void gblink_int_disable(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /*
- * NOTE: This is currently safe to run even with the exti workaround
- * in effect. It just disables the root EXTI interrupt source of the
- * given pin.
- */
- furi_hal_gpio_disable_int_callback(gblink->clk);
- }
- void *gblink_alloc(void)
- {
- struct gblink *gblink;
- /* Allocate and zero struct */
- gblink = malloc(sizeof(struct gblink));
- //gblink->spec = malloc(sizeof(struct gblink_spec));
- gblink->transfer_sem = furi_semaphore_alloc(1, 1);
- gblink->out_byte_sem = furi_semaphore_alloc(1, 1);
- gblink->start_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- /* Set defaults */
- gblink_pin_set_default(gblink, PINOUT_ORIGINAL);
- gblink_mode_set(gblink, GBLINK_MODE_GBC);
- gblink_clk_source_set(gblink, GBLINK_CLK_EXT);
- gblink_speed_set(gblink, GBLINK_SPD_8192HZ);
- gblink_timeout_set(gblink, 500);
- /* Set current time to start timeout calculations */
- gblink->time = DWT->CYCCNT;
- return gblink;
- }
- void gblink_start(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /* XXX: Check callback is valid */
- furi_mutex_acquire(gblink->start_mutex, FuriWaitForever);
- /* Set up pins */
- /* TODO: Set up a list of pins that are not safe to use with interrupts.
- * I do believe the main FURI GPIO struct has this data baked in so that
- * could be used. For now though, we're only checking for the MALVEKE
- * pinout which uses a clk pin that has its IRQ shared with the Okay
- * button.
- * See the work done in pokemon trade tool custom pinout selection for
- * an idea of how to check all that.
- */
- furi_hal_gpio_write(gblink->serout, false);
- furi_hal_gpio_init(gblink->serout, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
- furi_hal_gpio_write(gblink->serin, false);
- furi_hal_gpio_init(gblink->serin, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
- /* Set up interrupt on clock pin */
- if (gblink->clk == &gpio_ext_pb3) {
- /* The clock pin is on a pin that is not safe to set an interrupt
- * on, so we do a gross workaround to get an interrupt enabled
- * on that pin in a way that can be undone safely later with
- * no impact to the shared IRQ.
- */
- gblink->exti_workaround_handle = exti_workaround(gblink->clk, gblink_clk_isr, gblink);
- } else {
- /* This may not be needed after NFC refactor */
- furi_hal_gpio_remove_int_callback(gblink->clk);
- furi_hal_gpio_add_int_callback(gblink->clk, gblink_clk_isr, gblink);
- }
- /* The above immediately enables the interrupt, we don't want
- * that just yet and we want configure to handle it.
- */
- gblink_int_disable(gblink);
- gblink_clk_configure(gblink);
- }
- void gblink_stop(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /* If we can acquire the mutex, that means start was never actually
- * called. Crash.
- * XXX: Probably a bit harsh to just crash, can it gracefully recover
- * without too much effort?
- */
- if (furi_mutex_acquire(gblink->start_mutex, 0) == FuriStatusOk) {
- furi_crash();
- return;
- }
- if (gblink->clk == &gpio_ext_pb3) {
- /* This handles switching the IVT back and putting the EXTI
- * regs and pin regs in a valid state for normal use.
- */
- exti_workaround_undo(gblink->exti_workaround_handle);
- } else {
- /* Remove interrupt, set IO to sane state */
- furi_hal_gpio_remove_int_callback(gblink->clk);
- }
- furi_hal_gpio_init_simple(gblink->serin, GpioModeAnalog);
- furi_hal_gpio_init_simple(gblink->serout, GpioModeAnalog);
- furi_hal_gpio_init_simple(gblink->clk, GpioModeAnalog);
- furi_mutex_release(gblink->start_mutex);
- }
- void gblink_free(void *handle)
- {
- furi_assert(handle);
- struct gblink *gblink = handle;
- /* If we cannot acquire the mutex, that means the link was never properly
- * stopped. Crash.
- * XXX: Can this be gracefully handled?
- */
- if (furi_mutex_acquire(gblink->start_mutex, 0) != FuriStatusOk) {
- furi_crash();
- return;
- }
- furi_mutex_release(gblink->start_mutex);
- furi_mutex_free(gblink->start_mutex);
- furi_semaphore_free(gblink->transfer_sem);
- furi_semaphore_free(gblink->out_byte_sem);
- free(gblink);
- }
|