/**
 * @copyright (c) 2021 Noel Dom https://www.youtube.com/channel/UCINCDcQylATh2wS5BAYIBPg.
 *
 * @brief	Device Driver for DS1307 Real-time clock (RTC)
 * @file	ds1307.c
 * @version 0.1.0
 * @date	2021
 * @author 	Noel Dominguez.
 */


#include "ds1307.h"

#define DS1307_ADDRES		0x68
#define DS1307_SECONDS 		0x00
#define DS1307_MINUTES		0x01
#define DS1307_HOURS		0x02
#define DS1307_DAY			0X03
#define	DS1307_DATE			0x04
#define DS1307_MONTH		0x05
#define	DS1307_YEAR			0x06
#define	DS1307_CONTROL		0x07
#define DS1307_REG_UTC_HR	0x08
#define DS1307_REG_UTC_MIN	0x09
#define DS1307_REG_CENT    	0x10
#define DS1307_REG_RAM   	0x11
#define DS1307_TIMEOUT		1000


#define DS1307_HANDLER		hi2c1

typedef enum{
	DS1307_1HZ,
	DS1307_4096HZ,
	DS1307_8192HZ,
	DS1307_32768HZ
}ds1307_rate_t;


static ds1307_err_t ds1307_write_byte(uint8_t ds1307_reg_addres, uint8_t data);
static uint8_t ds1307_read_byte(uint8_t ds1307_reg_addres);
static uint8_t ds1307_bcd_decode(uint8_t data);
static uint8_t ds1307_bcd_encode(uint8_t data);

/*
 *
 	 Function designed to read embedded RAM data.
 	 Address range 0x08 to 0x3F
 	 56 bytes of data

 	 addr: address of data to read 0x08 to 0x3F
 	 data: pointer to data
 	 len: number of bytes of data to read

 */

uint8_t ds1307_read_user_RAM(uint8_t addr, uint8_t * data, uint8_t len){

	HAL_StatusTypeDef status = HAL_OK;

	for(uint8_t i = 0; i < len; i++){
		uint8_t resp = ds1307_read_byte(addr + i);
		*(data + i) = resp;
	}

	return status;
}

/*
 *
 	 Function designed to write embedded RAM data.
 	 Address range 0x08 to 0x3F
 	 56 bytes of data

 	 addr: address of data to read 0x08 to 0x3F
 	 data: pointer to data
 	 len: number of bytes of data to read

 */

uint8_t ds1307_write_user_RAM(uint8_t addr, uint8_t * data, uint8_t len){
	HAL_StatusTypeDef status = HAL_OK;

	for(uint8_t i = 0; i < len; i++){

		ds1307_write_byte(addr + i, *(data + i));
		HAL_Delay(5);

	}


	return status;
}

/**
 * @brief Write byte data from an specific address ds1307 RTC
 * 
 * @param ds1307_reg_addres: REG Address you can see more in datasheet page 4 figure 2
 * @param data 
 * @return ds1307_err_t: 0 if everything is ok
 */
static ds1307_err_t ds1307_write_byte(uint8_t ds1307_reg_addres, uint8_t data){

	uint8_t buff[2] = {ds1307_reg_addres,data};
	ds1307_err_t ret_val;
	ret_val = HAL_I2C_Master_Transmit(&DS1307_HANDLER, DS1307_ADDRES << 1, buff, 2, DS1307_TIMEOUT);

	return ret_val;
}
/**
 * @brief Read byte data from an specific address ds1307 RTC
 * 
 * @param ds1307_reg_addres: REG Address you can see more in datasheet page 4 figure 2
 * @return data: data read from reg address
 */
static uint8_t ds1307_read_byte(uint8_t ds1307_reg_addres){

	HAL_StatusTypeDef status = HAL_ERROR;
	uint8_t data;
	status = HAL_I2C_Master_Transmit(&DS1307_HANDLER, DS1307_ADDRES << 1, &ds1307_reg_addres, 1, DS1307_TIMEOUT);
	status = HAL_I2C_Master_Receive(&DS1307_HANDLER, DS1307_ADDRES << 1, &data, 1, DS1307_TIMEOUT);
	if(HAL_OK == status){
		return data;
	}
	else{
		return 255;
	}
}
/**
 * @brief BCD decode
 * 
 * @param data: Value to convert
 * @return uint8_t: data converted
 */
static uint8_t ds1307_bcd_decode(uint8_t data){
	return (((data & 0xf0) >> 4) * 10) + (data & 0x0f);
}
/**
 * @brief BCD Encode
 * 
 * @param data: Value to convert
 * @return uint8_t: data converted
 */
static uint8_t ds1307_bcd_encode(uint8_t data){
	return (data % 10 + ((data / 10) << 4));
}

/**
 * @brief Init ds1307
 * 
 */
void ds1307_init(void){
	ds1307_set_clock_halt(0);
}

/**
 * @brief To start the time and calendar, we must set the stop bit of the clock (CH) in 0, to stop, put the bit in 1
 * more information see datasheet on page 4
 * 
 * @param halt: 0 init, 1 stop
 */
void ds1307_set_clock_halt(uint8_t halt){
	uint8_t ch = (halt ? 1 << 7 : 0);
	ds1307_write_byte(DS1307_SECONDS, ch | (ds1307_read_byte(DS1307_SECONDS) & 0x7F));
}
/**
 * @brief ds1307_get_clock_halt more info datasheet page 4
 * 
 * @return uint8_t 
 */
uint8_t ds1307_get_clock_halt(void){
	uint8_t read = ds1307_read_byte(DS1307_SECONDS);
	if(read != 255){
		return (read & 0x80) >> 7;
	}
	else{
		return 255;
	}
}

/**
 * @brief ds1307_set_hour
 * 
 * @param hour 
 * @return ds1307_err_t 
 */
ds1307_err_t ds1307_set_hour(uint8_t hour){
	return ds1307_write_byte(DS1307_HOURS,ds1307_bcd_encode(hour & 0x3F));
}
/**
 * @brief ds1307_get_hour
 * 
 * @return uint8_t 
 */
uint8_t ds1307_get_hour(void){
	return ds1307_bcd_decode(ds1307_read_byte(DS1307_HOURS) & 0x3F);
}

/**
 * @brief ds1307_set_second
 * 
 * @param second 
 */
void ds1307_set_second(uint8_t second){
	uint8_t val = ds1307_get_clock_halt();
	ds1307_write_byte(DS1307_SECONDS, ds1307_bcd_encode(second | val));

}
/**
 * @brief ds1307_get_second
 * 
 * @return uint8_t 
 */
uint8_t ds1307_get_second(void){
	uint8_t decode = ds1307_read_byte(DS1307_SECONDS);
	if(decode != 255){
		return ds1307_bcd_decode(decode & 0x7F);
	}
	else{
		return 255;
	}
}
/**
 * @brief ds1307_set_minutes
 * 
 * @param minutes 
 */
void ds1307_set_minutes(uint8_t minutes){
	ds1307_write_byte(DS1307_MINUTES, ds1307_bcd_encode(minutes));
}
/**
 * @brief ds1307_get_minutes
 * 
 * @return uint8_t 
 */
uint8_t ds1307_get_minutes(void){
	return ds1307_bcd_decode(ds1307_read_byte(DS1307_MINUTES));

}
/**
 * @brief ds1307_set_day
 * 
 * @param day 
 */
void ds1307_set_day(uint8_t day){
	ds1307_write_byte(DS1307_DAY, ds1307_bcd_encode(day));
}
/**
 * @brief ds1307_get_day
 * 
 * @return ds1307_days_t 
 */
ds1307_days_t ds1307_get_day(void){
	return ds1307_read_byte(ds1307_bcd_decode(DS1307_DAY));
}


/**
 * @brief ds1307_set_date
 * 
 * @param date 
 */
void ds1307_set_date(uint8_t date){
	ds1307_write_byte(DS1307_DATE, ds1307_bcd_encode(date));
}
/**
 * @brief 
 * 
 * @return uint8_t 
 */
uint8_t ds1307_get_date(void){
	return ds1307_bcd_decode(ds1307_read_byte(DS1307_DATE));
}

/**
 * @brief ds1307_set_month
 * 
 * @param month 
 */
void ds1307_set_month(ds1307_months_t month){
	ds1307_write_byte(DS1307_MONTH, ds1307_bcd_encode(month));
}
/**
 * @brief ds1307_get_month
 * 
 * @return ds1307_months_t 
 */
ds1307_months_t ds1307_get_month(void){
	return ds1307_read_byte(ds1307_bcd_decode(DS1307_MONTH));
}
/**
 * @brief ds1307_set_year
 * 
 * @param year setup year
 */
void ds1307_set_year(uint16_t year){
	ds1307_write_byte(DS1307_REG_CENT, year / 100);
	ds1307_write_byte(DS1307_YEAR, ds1307_bcd_encode(year % 100));
}
/**
 * @brief ds1307_get_year
 * 
 * @return uint16_t: current year
 */
uint16_t ds1307_get_year(void){
	uint16_t cent = ds1307_read_byte(DS1307_REG_CENT) * 100;
	return ds1307_bcd_decode(ds1307_read_byte(DS1307_YEAR)) + cent;

}
/**
 * @brief Set your time zone more info at https://everytimezone.com/
 * 
 * @param hr: current hour
 * @param min: current min
 */
void ds1307_set_time_zone(int8_t hr, uint8_t min){
	ds1307_write_byte(DS1307_REG_UTC_HR, hr);
	ds1307_write_byte(DS1307_REG_UTC_MIN, min);
}
/**
 * @brief Get the actual timezone-hour configured in the rtc
 * 
 * @return int8_t actual time zone
 */
int8_t ds1307_get_time_zone_hour(void){
	return ds1307_read_byte(DS1307_REG_UTC_HR);
}
/**
 * @brief  Get the actual timezone-min configured in the rtc
 * 
 * @return int8_t: actual time zone
 */
int8_t ds1307_get_time_zone_min(void){
	return ds1307_read_byte(DS1307_REG_UTC_MIN);
}


/**
 * @brief Update ds1307 data
 * 
 * @param dev: ds1307 pointer
 */
void ds1307_update(ds1307_dev_t *ds1307_dev){
	uint8_t val = 0;

	val = ds1307_get_second();
	if(val != 255){
		ds1307_dev->seconds = val;
	}

	val = ds1307_get_minutes();
	if(val != 255){
			ds1307_dev->minutes = val;
	}

	ds1307_dev->hours = ds1307_get_hour();
	ds1307_dev->day = ds1307_get_day();
	ds1307_dev->date = ds1307_get_date();
	ds1307_dev->month = ds1307_get_month();
	ds1307_dev->year = ds1307_get_year();
	ds1307_dev->t_zone_hour = ds1307_get_time_zone_hour();
	ds1307_dev->t_zone_min = ds1307_get_time_zone_min();
}
/**
 * @brief Use this function to configure the DS1307, you only need to call this function once on the the first run of your RTC.
 * 
 * @param seconds
 * @param minutes 
 * @param hours 
 * @param day 
 * @param date 
 * @param month 
 * @param year 
 * @param t_zone_hour 
 * @param t_zone_min 
 */
void ds1307_config(uint8_t seconds, uint8_t minutes, uint8_t hours,ds1307_days_t day, uint8_t date,
					ds1307_months_t month, uint16_t year, int8_t t_zone_hour, int8_t t_zone_min)
{
	ds1307_set_second(seconds);
	ds1307_set_minutes(minutes);
	ds1307_set_hour(hours);
	ds1307_set_day(day);
	ds1307_set_date(date);
	ds1307_set_month(month);
	ds1307_set_year(year);
	ds1307_set_time_zone(t_zone_hour, t_zone_min);

}

/**
 * @brief This function helps to scan all connected devices on our I2C peripheral
 * 
 * @param I2Chnd: I2C Handles
 * @param delay_: Delay between scanning
 */
void start_i2c_scan(I2C_HandleTypeDef *I2Chnd, uint32_t delay_){

	HAL_StatusTypeDef status;
	uint8_t no_devices = 0;
	//DBG_print("\n\r [ I2C Scanner v0.1 ]");

	for (uint8_t i = 0; i < 128; i++){

		status = HAL_I2C_IsDeviceReady(I2Chnd , (uint16_t)(i<<1), 2, 2);

		if ( status != HAL_OK){
			//DBG_print(".\n\r");
		} else {
			//DBG_print("\n\rDevice found! Address: 0x%X",i);
			++no_devices;
		}

		HAL_Delay(delay_);

	}

	if (!no_devices){
		//DBG_print("\n\r No Devices found!");
	} else {
		//DBG_print("\n\r Total Devices found: %d",no_devices);

	}

}
