/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Damien P. George
 *
 * 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 "py/mphal.h"
#include "hub_display.h"

// Map pixel number 0-24, and 25-36 to TLC bit number.
static const uint8_t hub_display_pixel_map[] = {
    // 5x5 display
    9,  11,   6,   1,  14,
    10,  19,   8,   0,  26,
    23,  18,   3,   2,  24,
    21,  20,  15,  13,  25,
    22,   7,  17,  12,  38,
    // RGB Bluetooth button
    27, 28, 29,
    // RGB middle button (left and right)
    39, 40, 41, 42, 43, 44,
    // RGB battery indicator
    45, 46, 47,
};

static bool hub_display_init;
static uint8_t hub_display_gs_state[96];

static void hub_display_tim_init(void) {
    // TLC maximum GSCLK freq: 33MHz

    // tim12, ch2, pwm

    TIM_TypeDef *tim = TIM12;

    __HAL_RCC_TIM12_CLK_ENABLE();
    tim->CR1 = TIM_CR1_ARPE;

    // Configure PWM mode.
    uint32_t ch = 1; // ch2
    uint32_t reg = 6 << TIM_CCMR1_OC1M_Pos // PWM1 mode
        | 1 << TIM_CCMR1_OC1PE_Pos // preload enabled
        | 0 << TIM_CCMR1_CC1S_Pos // output mode
    ;
    uint32_t shift = 8 * (ch & 1);
    tim->CCMR1 = (tim->CCMR1 & ~(0xff << shift)) | reg << shift;

    // Enable output on pin, active high for normal channel.
    reg = TIM_CCER_CC1E;
    shift = 4 * ch;
    tim->CCER = (tim->CCER & ~(0xf << shift)) | reg << shift;

    // Enable the timer if it's not already running.
    tim->CR1 |= TIM_CR1_CEN;

    // fast
    tim->PSC = 0;
    tim->ARR = 2;

    // 50% duty
    tim->CCR2 = 2;
    tim->EGR = 1; // UG

    mp_hal_pin_config(pin_B15, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, 9);
}

static void hub_display_spi_init(void) {
    // TLC maximum SPI freq: 25MHz

    SPI_TypeDef *spi = SPI1;

    __HAL_RCC_SPI1_CLK_ENABLE();
    spi->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | 0 << SPI_CR1_BR_Pos | SPI_CR1_MSTR;
    spi->CR1 |= SPI_CR1_SPE;

    mp_hal_pin_config(pin_A5, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, 5);
    mp_hal_pin_config(pin_A6, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, 5);
    mp_hal_pin_config(pin_A7, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, 5);
}

static void hub_display_spi_write(uint8_t value) {
    SPI_TypeDef *spi = SPI1;
    spi->DR = value;
    while (!(spi->SR & SPI_SR_TXE)) {
    }
}

// dc: dot control
// mc: maximum current
// bc: global brightness control
// fc: function control
static void hub_display_latch_ctrl(uint8_t dc, uint32_t mc, uint32_t bc, uint8_t fc) {
    hub_display_spi_write(1);                   // bit 768
    hub_display_spi_write(0x96);                // bits 760-767
    for (int i = 0; i < 48; ++i) {
        hub_display_spi_write(0);
    }
    hub_display_spi_write(fc >> 2);             // bits 368-375
    hub_display_spi_write(fc << 6 | bc >> 15);  // bits 360-367
    hub_display_spi_write(bc >> 7);             // bits 352-359
    hub_display_spi_write(bc << 1 | mc >> 8);   // bits 344-351
    hub_display_spi_write(mc);                  // bits 336-343
    for (int i = 0; i < 42; ++i) {
        hub_display_spi_write(dc);
    }
    mp_hal_pin_high(pin_A15);
    mp_hal_delay_us(1);
    mp_hal_pin_low(pin_A15);
}

void hub_display_set(uint8_t led, uint16_t value) {
    led = hub_display_pixel_map[led];
    hub_display_gs_state[led * 2] = value;
    hub_display_gs_state[led * 2 + 1] = value >> 8;
}

void hub_display_update(void) {
    if (!hub_display_init) {
        return;
    }
    hub_display_spi_write(0);
    for (int i = 0; i < 96; ++i) {
        hub_display_spi_write(hub_display_gs_state[95 - i]);
    }
    mp_hal_pin_high(pin_A15);
    mp_hal_delay_us(1);
    mp_hal_pin_low(pin_A15);
}

void hub_display_on(void) {
    if (hub_display_init) {
        return;
    }
    mp_hal_pin_output(pin_A15);
    mp_hal_pin_low(pin_A15);
    hub_display_spi_init();
    for (int i = 0; i < 2; ++i) {
        hub_display_latch_ctrl(0xff, 0, 0x1fffff, 0x11);
    }
    hub_display_tim_init();
    hub_display_init = true;
}

void hub_display_off(void) {
    if (!hub_display_init) {
        return;
    }
    __HAL_RCC_TIM12_CLK_DISABLE();
    __HAL_RCC_TIM12_FORCE_RESET();
    __HAL_RCC_TIM12_RELEASE_RESET();
    __HAL_RCC_SPI1_CLK_DISABLE();
    __HAL_RCC_SPI1_FORCE_RESET();
    __HAL_RCC_SPI1_RELEASE_RESET();
    mp_hal_pin_config(pin_A5, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
    mp_hal_pin_config(pin_A6, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
    mp_hal_pin_config(pin_A7, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
    mp_hal_pin_config(pin_A15, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
    mp_hal_pin_config(pin_B15, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
    hub_display_init = false;
}
