struct __attribute__( ( packed ) ) date_time
{
  uint16_t      year;
  uint8_t       month;
  uint8_t       day;
  uint8_t       week_day;
  uint8_t       hour;
  uint8_t       minute;
  uint8_t       second;
};

static int    inline
check_for_bit( volatile uint32_t *reg, int bit, int state )
{
  int           i;

  state = !!state;
  for( i = 0; i < 10000000; i++ )
    if( ( *reg >> bit & 1 ) == state )	// wait for bit set/reset
      return 0;
  return -1;
}

static int
i_set_RTC_regs( uint32_t date, uint32_t time )
{
  PWR->CR1 |= 1 << 8;		// set DPD bit
  if( check_for_bit( &PWR->CR1, 8, 1 ) )
    return -1;
  RTC->WPR = 0xCA;		// disable write protection
  RTC->WPR = 0x53;
  RTC->ISR |= 1 << 7;		// set INIT
  if( check_for_bit( &RTC->ISR, 6, 1 ) )	// wait for INITF
    return -2;
  RTC->PRER = 0x7F00FF;
  RTC->DR = date;
  RTC->TR = time;
  RTC->CR = 0;
  RTC->ISR &= ~( 1 << 7 );	// clear INIT
  if( check_for_bit( &RTC->ISR, 6, 0 ) )	// wait for INITF 
    return -3;
  RTC->WPR = 0xFF;		// enable write protection
  PWR->CR1 &= ~( 1 << 8 );	// clear DPD bit
  if( check_for_bit( &PWR->CR1, 8, 0 ) )
    return -4;
  return 0;
}

int
set_RTC_regs( struct date_time *dt )
{
  uint32_t      date, time;
  uint32_t      year, month, day, week_day, hour, minute, second;

  year = dt->year % 100;
  month = dt->month;
  if( month < 1 )
    month = 1;
  if( month > 12 )
    month = 12;
  day = dt->day;
  if( day < 1 )
    day = 1;
  if( day > 31 )
    day = 31;
  week_day = dt->week_day;
  if( week_day < 1 )
    week_day = 1;
  if( week_day > 7 )
    week_day = 7;
  date = ( year / 10 ) << 20 | ( year % 10 ) << 16 |
      week_day << 13 |
      ( month / 10 ) << 12 | ( month % 10 ) << 8 |
      ( day / 10 ) << 4 | ( day % 10 ) << 0;
  hour = dt->hour % 24;
  minute = dt->minute % 60;
  second = dt->second % 60;
  time = ( hour / 10 ) << 20 | ( hour % 10 ) << 16 |
      ( minute / 10 ) << 12 | ( minute % 10 ) << 8 |
      ( second / 10 ) << 4 | ( second % 10 ) << 0;
  return i_set_RTC_regs( date, time );
}

static int
init_rtc( void )
{
  struct date_time dt;

  if( ( RCC->BDCR & 0x8303 ) != 0x8103 )
  {
    PWR->CR1 |= 1 << 8;		// set DPD bit
    if( check_for_bit( &PWR->CR1, 8, 1 ) )
      return -5;
    RCC->BDCR = 0x8109;		// enable RTC
    if( check_for_bit( &RCC->BDCR, 1, 1 ) )	// wait for LSE ready
      return -6;
    PWR->CR1 &= ~( 1 << 8 );	// clear DPD bit
    if( check_for_bit( &PWR->CR1, 8, 0 ) )
      return -7;
  }
  if( ( RTC->ISR & 1 << 4 ) == 0 || RTC->DR >> 20 != 2
    || RTC->CR & 0x1E0FF00 != 0 )
  {
    // calendar not initialized
    // set a default starting date
    dt.year = 2021;
    dt.month = 2;
    dt.day = 25;
    dt.week_day = 4;
    dt.hour = 16;
    dt.minute = 16;
    dt.second = 16;
    return set_RTC_regs( &dt );
  }
  return 0;
}

