/*
 *
 * Modified from unicore-mx SDRAM support
 * 
 * Copyright (C) 2014 Chuck McManis <cmcmanis@mcmanis.com>
 * Copyright (C) 2016 Maxime Vincent <maxime.vince@gmail.com>
 * Copyright (C) 2016-2023 Daniele Lacamera <root@danielinux.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 *      Original authors: 
 *          Maxime Vincent <maxime.vince@gmail.com>
 *          Chuck McManis <cmcmanis@mcmanis.com>
 *          Daniele Lacamera <root@danielinux.net>
 * 
 *
 */

#include <stdint.h>
#include <unicore-mx/stm32/gpio.h>
#include <unicore-mx/stm32/rcc.h>
#include <unicore-mx/stm32/fsmc.h>

extern volatile uint32_t jiffies;

#define SDRAM_BASE_ADDRESS ((uint8_t *)(0xC0000000))

/*
 * This is just syntactic sugar but it helps, all of these
 * GPIO pins get configured in exactly the same way.
 */
static const struct {
	uint32_t	gpio;
	uint16_t	pins;
} sdram_pins[6] = {
	{GPIOC, GPIO3 },
	{GPIOD, GPIO0 | GPIO1 | GPIO3 | GPIO8 | GPIO9 | GPIO10 | GPIO14 | GPIO15},
	{GPIOE, GPIO0 | GPIO1 | GPIO7 | GPIO8 | GPIO9 | GPIO10 |
			GPIO11 | GPIO12 | GPIO13 | GPIO14 | GPIO15 },
	{GPIOF, GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | GPIO5 | GPIO11 |
			GPIO12 | GPIO13 | GPIO14 | GPIO15 },
	{GPIOG, GPIO0 | GPIO1 | GPIO4 | GPIO5 | GPIO8 | GPIO15},
	{GPIOH, GPIO3 | GPIO5 }
};

static struct sdram_timing timing = {
    .trcd = 2,      /* RCD Delay */
    .trp = 2,       /* RP Delay */
    .twr = 2,       /* Write Recovery Time */
    .trc = 7,       /* Row Cycle Delay */
    .tras = 4,      /* Self Refresh Time */
    .txsr = 7,      /* Exit Self Refresh Time */
    .tmrd = 2,      /* Load to Active Delay */
};

/*
 * Initialize the SD RAM controller.
 */
void sdram_init(void)
{
    int i, now;
    uint32_t cr_tmp, tr_tmp; /* control, timing registers */

	/*
	* First all the GPIO pins that end up as SDRAM pins
	*/
	rcc_periph_clock_enable(RCC_GPIOC);
	rcc_periph_clock_enable(RCC_GPIOD);
	rcc_periph_clock_enable(RCC_GPIOE);
	rcc_periph_clock_enable(RCC_GPIOF);
	rcc_periph_clock_enable(RCC_GPIOG);
	rcc_periph_clock_enable(RCC_GPIOH);

	for (i = 0; i < 6; i++) {
		gpio_mode_setup(sdram_pins[i].gpio,
				GPIO_MODE_AF, GPIO_PUPD_NONE,
				sdram_pins[i].pins);
		gpio_set_output_options(sdram_pins[i].gpio, GPIO_OTYPE_PP,
					GPIO_OSPEED_100MHZ, sdram_pins[i].pins);
		gpio_set_af(sdram_pins[i].gpio, GPIO_AF12, sdram_pins[i].pins);
	}

    /* Enable the SDRAM Controller */
#if 1
    rcc_periph_clock_enable(RCC_FMC);
#else
    rcc_peripheral_enable_clock(&RCC_AHB3ENR, RCC_AHB3ENR_FMCEN);
#endif

    /* Note the STM32F746G-DISCO board has the ram attached to bank 1 */
    /* Timing parameters computed for a ~200 MHz  clock */
    /* These parameters are specific to the SDRAM chip on the board */

    cr_tmp  = FMC_SDCR_RPIPE_NONE;
    cr_tmp |= FMC_SDCR_SDCLK_2HCLK;
    cr_tmp |= FMC_SDCR_CAS_2CYC;
    cr_tmp |= FMC_SDCR_NB4;
    cr_tmp |= FMC_SDCR_MWID_16b;
    cr_tmp |= FMC_SDCR_NR_12;
    cr_tmp |= FMC_SDCR_NC_8;

	/* We're programming BANK 1
     * Per the manual some of the parameters only work in CR1 and TR1
     * So when using BANK2: we need pull those off and put them in the right place.
     * FMC_SDCR1 |= (cr_tmp); // & FMC_SDCR_DNC_MASK);
     * FMC_SDCR2 = cr_tmp;
     */
	FMC_SDCR1 = (cr_tmp);

    tr_tmp = sdram_timing(&timing);
	FMC_SDTR1 = (tr_tmp);

    /* Now start up the Controller per the manual
     *  - Clock config enable
     *  - PALL state
     *  - set auto refresh
     *  - Load the Mode Register
     */
    sdram_command(SDRAM_BANK1, SDRAM_CLK_CONF, 1, 0);

    /* sleep at least 100uS */
    now = jiffies;
    //while (now + 2 > jiffies) {};

    sdram_command(SDRAM_BANK1, SDRAM_PALL, 1, 0);
    sdram_command(SDRAM_BANK1, SDRAM_AUTO_REFRESH, 8, 0);
    tr_tmp = SDRAM_MODE_BURST_LENGTH_1              |
                SDRAM_MODE_BURST_TYPE_SEQUENTIAL    |
                SDRAM_MODE_CAS_LATENCY_2        |
                SDRAM_MODE_OPERATING_MODE_STANDARD  |
                SDRAM_MODE_WRITEBURST_MODE_SINGLE;
    sdram_command(SDRAM_BANK1, SDRAM_LOAD_MODE, 1, tr_tmp);

    /*
     * set the refresh counter to insure we kick off an
     * auto refresh often enough to prevent data loss.
     */
    FMC_SDRTR = ((uint32_t)0x0603);   /* SDRAM refresh counter (100Mhz SD clock) */
    /* and Poof! a 8 megabytes of ram shows up in the address space */

    /* Disable write protection */
    FMC_SDCR1 &= ~FMC_SDCR_WP_ENABLE;
}

