/*
 * FMC driver
 */


/* include path: install_root/linux/include */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/err.h>
#include <asm/uaccess.h>

#include "stm32f4xx.h"
#include "stm32f4xx_hal_rcc.h"
#include "stm32f4xx_ll_fmc.h"
#include "stm32f4xx_hal_gpio.h"
#include "stm32f4xx_hal_sram.h"



static int major = 102;
static int minor = 0;
static dev_t devno;
static struct cdev *fmc_cdev = NULL;
static int count  = 1;
struct class *my_class = NULL;
struct device *dev = NULL;
SRAM_HandleTypeDef hsram;

#define DEVNAME "fmc_cpld"

static int fmc_open(struct inode *inode, struct file *filep);
static int fmc_close(struct inode *inode, struct file *filep);
static ssize_t fmc_read(struct file *filep, char __user *buf, size_t size, loff_t *offset);
static ssize_t fmc_write(struct file *filep, const char __user *buf, size_t size, loff_t *offset);
static int fmc_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg);

static struct file_operations fmc_ops = 
{
	.owner = THIS_MODULE,
	.open = fmc_open,
	.release = fmc_close,
	.read = fmc_read,
	.write = fmc_write,
	.ioctl = fmc_ioctl,
};

static int fmc_open(struct inode *inode, struct file *filep)
{
	printk("fmc_open!\n");
	__FMC_NORSRAM_ENABLE(FMC_NORSRAM_DEVICE,FMC_NORSRAM_BANK1);
	return 0;
}

static int fmc_close(struct inode *inode, struct file *filep)
{
	printk("fmc_close!\n");
	__FMC_NORSRAM_DISABLE(FMC_NORSRAM_DEVICE, FMC_NORSRAM_BANK1);
	return 0;
}

static ssize_t fmc_read(struct file *filep, char __user *buf, size_t size, loff_t *offset)
{
	printk("fmc_read!\n");
	return 0;
}

static ssize_t fmc_write(struct file *filep, const char __user *buf, size_t size, loff_t *offset)
{
/*	uint32_t i;
	uint8_t	tmp_buf[100];
	printk("fmc_write");
	for(i=0;i<size;i++)
		tmp_buf[i] = *(uint8_t*)(offset+i);
	copy_to_user();*/
	return 0;
}


typedef struct {
	uint32_t address;
	uint8_t  data;
} fmc_cpld_t;

static int fmc_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, unsigned long arg)
{
	fmc_cpld_t fmc_arg;	
	printk("fmc_ioctl!\n");
	
	if ( copy_from_user(&fmc_arg, (void*)arg, sizeof(fmc_cpld_t)) )
		return -EFAULT;

	switch(cmd)
	{
		case 0:	//write one byte to cpld
			printk("write %#x to %#x\n", fmc_arg.data, fmc_arg.address);
			HAL_SRAM_Write_8b(&hsram, (uint32_t*)fmc_arg.address, &fmc_arg.data, 1);
			break;
		case 1:	//read one byte from cpld
			HAL_SRAM_Read_8b(&hsram, (uint32_t*)fmc_arg.address, &fmc_arg.data, 1);
			printk("read %#x from %#x\n", fmc_arg.data, fmc_arg.address);
			if(copy_to_user((void*)arg, &fmc_arg, sizeof(fmc_cpld_t)) != 0)
				return -EFAULT;
			break;
		default:
			printk("Invalid command!\n");
			break;	
	}
	
	return 0;
}

int fmc_hw_init(void)
{
	FMC_NORSRAM_TimingTypeDef SRAM_Timing;
	GPIO_InitTypeDef  GPIO_Init_Structure;

	/*##-1- Enable peripherals and GPIO Clocks #################################*/
	/* Enable GPIO clocks */
	__HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOD_CLK_ENABLE();
	__HAL_RCC_GPIOE_CLK_ENABLE();
	__HAL_RCC_GPIOF_CLK_ENABLE();
	__HAL_RCC_GPIOG_CLK_ENABLE();
	__HAL_RCC_GPIOI_CLK_ENABLE();
	/* Enable FMC clock */
	__HAL_RCC_FMC_CLK_ENABLE();
	                              
	/*##-2- Configure peripheral GPIO ##########################################*/  
	/*-- GPIOs Configuration -----------------------------------------------------*/
	/*
	 * +-------------------+--------------------+--------------------+--------------------+
	 * +                       SRAM(CPLD) pins assignment                               +
	 * +-------------------+--------------------+--------------------+--------------------+
	 * | PD0  <-> FMC_D2   | PE0  <-> xxxxxxxx  | PF0  <-> FMC_A0    | PG0  <-> FMC_A10   |
	 * | PD1  <-> FMC_D3   | PE1  <-> xxxxxxxx  | PF1  <-> FMC_A1    | PG1  <-> FMC_A11   |
	 * | PD8  <-> FMC_D13  | PE7  <-> FMC_D4    | PF2  <-> FMC_A2    | PG4  <-> xxxxxxx   |
	 * | PD9  <-> FMC_D14  | PE8  <-> FMC_D5    | PF3  <-> FMC_A3    | PG5  <-> xxxxxxx   |
	 * | PD10 <-> FMC_D15  | PE9  <-> FMC_D6    | PF4  <-> FMC_A4    | PG9  <-> xxxxxxx   |
	 * | PD14 <-> FMC_D0   | PE10 <-> FMC_D7    | PF5  <-> FMC_A5    | PG10 <-> xxxxxxx   |
	 * | PD15 <-> FMC_D1   | PE11 <-> FMC_D8    | xxxx <-> xxxxxxxx  | PG12 <-> xxxxxxx 
	 * | PD4  <-> FMC_NOE  | PE12 <-> FMC_D9    | PF12 <-> FMC_A6    | 
	 * | PD5  <-> FMC_NWE  | PE13 <-> FMC_D10   | PF13 <-> FMC_A7    |    
	 * | PD7  <-> FMC_NE1  | PE14 <-> FMC_D11   | PF14 <-> FMC_A8    |
	 *                     | PE15 <-> FMC_D12   | PF15 <-> FMC_A9    |
	 *+-------------------+--------------------+--------------------+--------------------+
	   | PI4  <-> xxxxxxx
	   | PI5  <-> xxxxxxx
	*/
				    
	/* Common GPIO configuration */
	GPIO_Init_Structure.Mode  = GPIO_MODE_AF_PP;
	GPIO_Init_Structure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_Init_Structure.Pull  = GPIO_NOPULL;
	GPIO_Init_Structure.Alternate = GPIO_AF12_FMC;
										  
	/* GPIOD configuration */
	GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1  | GPIO_PIN_8 |
	                          GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 |
	                          GPIO_PIN_15| GPIO_PIN_4  | GPIO_PIN_5 | 
	    					  GPIO_PIN_7;
	HAL_GPIO_Init(GPIOD, &GPIO_Init_Structure);

	/* GPIOE configuration */
	GPIO_Init_Structure.Pin = GPIO_PIN_7 |
	                          GPIO_PIN_8  | GPIO_PIN_9  | GPIO_PIN_10 |
	                          GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 |
		                      GPIO_PIN_14 | GPIO_PIN_15;
	HAL_GPIO_Init(GPIOE, &GPIO_Init_Structure);

	/* GPIOF configuration */
	GPIO_Init_Structure.Pin = GPIO_PIN_0  | GPIO_PIN_1 | GPIO_PIN_2 | 
	                          GPIO_PIN_3  | GPIO_PIN_4 | GPIO_PIN_5 |
	                          GPIO_PIN_12 | GPIO_PIN_13 |
	                          GPIO_PIN_14 | GPIO_PIN_15;      
	HAL_GPIO_Init(GPIOF, &GPIO_Init_Structure);

	/* GPIOG configuration */
	GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 ;
	HAL_GPIO_Init(GPIOG, &GPIO_Init_Structure);    
																	
//	/* GPIOI configuration */
//	GPIO_Init_Structure.Pin = GPIO_PIN_4 | GPIO_PIN_5;
//	HAL_GPIO_Init(GPIOI, &GPIO_Init_Structure);  

	hsram.Instance = FMC_NORSRAM_DEVICE;
	
	SRAM_Timing.AddressSetupTime = 3;
	SRAM_Timing.AddressHoldTime = 2;
	SRAM_Timing.DataSetupTime = 6;
	SRAM_Timing.BusTurnAroundDuration = 3;
	SRAM_Timing.CLKDivision = 2;
	SRAM_Timing.DataLatency = 0;
	SRAM_Timing.AccessMode = FMC_ACCESS_MODE_A;
									
	//fmc config
	hsram.Init.NSBank = FMC_NORSRAM_BANK2;
	hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
	hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
	hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
	hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
	hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
	hsram.Init.WrapMode = FMC_WRAP_MODE_DISABLE;
	hsram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
	hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
	hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
	hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
	hsram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE;
	hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
	hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
	//hsram.Init.WriteFifo = FMC_WRITE_FIFO_DISABLE;
	hsram.Init.PageSize = FMC_PAGE_SIZE_NONE;
	/* Initialize the SDRAM controller */
	if(HAL_SRAM_Init(&hsram, &SRAM_Timing, NULL) != HAL_OK)
	{
	    printk(" FMC Initialization Error\n");
		return -1;
	}
	return 0;
}

static int __init fmc_init(void)
{
	int ret;

	printk("fmc_cpld driver init!\n");
	fmc_cdev = cdev_alloc();
	if(fmc_cdev == NULL)
	{
		return -ENOMEM;
	}

	cdev_init(fmc_cdev, &fmc_ops);
	devno = MKDEV(major, minor);
	ret = register_chrdev_region(devno, count, DEVNAME);
	if(ret)
	{
		goto ERR_STEP;
	}

	ret = cdev_add(fmc_cdev, devno, count);
	if(ret)
	{
		goto ERR_STEP1;
	}

	if(fmc_hw_init() != 0)
		goto ERR_STEP1;

	return 0;

ERR_STEP1:
	unregister_chrdev_region(devno, count);
ERR_STEP:
	cdev_del(fmc_cdev);

	return 0;
}

static void __exit fmc_exit(void)
{
	unregister_chrdev_region(MKDEV(major, minor), count);
	cdev_del(fmc_cdev);
}

module_init(fmc_init);
module_exit(fmc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jimbo_zhang");
MODULE_DESCRIPTION("fmc_cpld driver");
