/** * @file * * @ingroup RTEMSBSPsARMCycV */ /* * Copyright (c) 2014 embedded brains GmbH. All rights reserved. * * embedded brains GmbH * Dornierstr. 4 * 82178 Puchheim * Germany * * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.org/license/LICENSE. */ /* * Driver for the DS1339 RTC (Maxim Semiconductors) -> RTC1 * and the M41ST87 RTC (ST Microelectronics) -> RTC2 * * Please note the following points: * - The day of week is ignored. * - The century bit is interpreted the following way: * - century not set: TOD_BASE_YEAR .. 1999 * - century set: 2000 .. 2099 * - century not set: 2100 .. (TOD_BASE_YEAR + 200) */ #include #include #include #include #include #include #include #include #include #define ALTERA_CYCLONE_V_RTC_NUMBER 2 /* ******************************* DS1339 ********************************** */ #define DS1339_I2C_ADDRESS (0xD0 >> 1) /* 7-bit addressing! */ #define DS1339_I2C_BUS_DEVICE "/dev/i2c0" #define DS1339_ADDR_TIME 0x00 #define DS1339_ADDR_CTRL 0x0E #define DS1339_CTRL_EOSC 0x80 #define DS1339_CTRL_BBSQI 0x20 #define DS1339_CTRL_RS2 0x10 #define DS1339_CTRL_RS1 0x08 #define DS1339_CTRL_INTCN 0x04 #define DS1339_CTRL_A2IE 0x02 #define DS1339_CTRL_A1IE 0x01 #define DS1339_CTRL_DEFAULT (0x00) #define DS1339_ADDR_STATUS 0x0F #define DS1339_STATUS_OSF 0x80 #define DS1339_STATUS_A2F 0x02 #define DS1339_STATUS_A1F 0x01 #define DS1339_STATUS_CLEAR (0x00) #define DS1339_ADDR_TRICKLE_CHARGE 0x10 typedef struct { uint8_t seconds; uint8_t minutes; uint8_t hours; #define DS1339_HOURS_12_24_FLAG 0x40 #define DS1339_HOURS_AM_PM_FLAG_OR_20_HOURS 0x20 #define DS1339_HOURS_10_HOURS 0x10 uint8_t weekday; uint8_t date; uint8_t month; #define DS1339_MONTH_CENTURY 0x80 uint8_t year; } ds1339_time_t; /* The longest write transmission is writing the time + one address bit */ #define DS1339_MAX_WRITE_SIZE (sizeof(ds1339_time_t) + 1) /* Functions for converting the fields */ static unsigned int ds1339_get_seconds(ds1339_time_t* time) { uint8_t tens = time->seconds >> 4; uint8_t ones = time->seconds & 0x0F; return tens * 10 + ones; } static unsigned int ds1339_get_minutes(ds1339_time_t* time) { uint8_t tens = time->minutes >> 4; uint8_t ones = time->minutes & 0x0F; return tens * 10 + ones; } static unsigned int ds1339_get_hours(ds1339_time_t* time) { uint8_t value = time->hours & 0x0F; if (time->hours & DS1339_HOURS_10_HOURS) { value += 10; } if (time->hours & DS1339_HOURS_AM_PM_FLAG_OR_20_HOURS) { if (time->hours & DS1339_HOURS_12_24_FLAG) value += 12; else value += 20; } return value; } static unsigned int ds1339_get_day_of_month(ds1339_time_t* time) { uint8_t tens = time->date >> 4; uint8_t ones = time->date & 0x0F; return tens * 10 + ones; } static unsigned int ds1339_get_month(ds1339_time_t* time) { uint8_t tens = (time->month >> 4) & 0x07; uint8_t ones = time->month & 0x0F; return tens * 10 + ones; } static unsigned int ds1339_get_year(ds1339_time_t* time) { unsigned int year = 1900; year += (time->year >> 4) * 10; year += time->year & 0x0F; if (time->month & DS1339_MONTH_CENTURY) year += 100; if (year < TOD_BASE_YEAR) year += 200; return year; } static void ds1339_set_time(ds1339_time_t* time, unsigned int second, unsigned int minute, unsigned int hour, unsigned int day, unsigned int month, unsigned int year) { unsigned int tens; unsigned int ones; uint8_t century = 0; tens = second / 10; ones = second % 10; time->seconds = tens << 4 | ones; tens = minute / 10; ones = minute % 10; time->minutes = tens << 4 | ones; tens = hour / 10; ones = hour % 10; time->hours = tens << 4 | ones; /* Weekday is not used. Therefore it can be set to an arbitrary valid value */ time->weekday = 1; tens = day / 10; ones = day % 10; time->date = tens << 4 | ones; tens = month / 10; ones = month % 10; if ((year >= 2000) && (year < 2100)) century = DS1339_MONTH_CENTURY; time->month = century | tens << 4 | ones; tens = (year % 100) / 10; ones = year % 10; time->year = tens << 4 | ones; } static rtems_status_code ds1339_open_file(int* fd) { int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; *fd = open(DS1339_I2C_BUS_DEVICE, O_RDWR); if (*fd == -1) sc = RTEMS_IO_ERROR; if (sc == RTEMS_SUCCESSFUL) { rv = ioctl(*fd, I2C_IOC_SET_SLAVE_ADDRESS, DS1339_I2C_ADDRESS); if (rv == -1) sc = RTEMS_IO_ERROR; } return sc; } /* Read size bytes from ds1339 register address addr to buf. */ static rtems_status_code ds1339_read(uint8_t addr, void* buf, size_t size) { int fd = -1; int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; sc = ds1339_open_file(&fd); if (sc == RTEMS_SUCCESSFUL) { rv = write(fd, &addr, sizeof(addr)); if (rv != sizeof(addr)) sc = RTEMS_IO_ERROR; } if (sc == RTEMS_SUCCESSFUL) { rv = read(fd, buf, size); if (rv != size) sc = RTEMS_IO_ERROR; } rv = close(fd); if (rv != 0) sc = RTEMS_IO_ERROR; return sc; } /* Write size bytes from buf to ds1339 register address addr. */ static rtems_status_code ds1339_write(uint8_t addr, void* buf, size_t size) { int fd = -1; int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; /* The driver never writes many bytes. Therefore it should be less expensive * to reserve the maximum number of bytes that will be written in one go than * use a malloc. */ uint8_t local_buf[DS1339_MAX_WRITE_SIZE]; int write_size = size + 1; assert(write_size <= DS1339_MAX_WRITE_SIZE); local_buf[0] = addr; memcpy(&local_buf[1], buf, size); sc = ds1339_open_file(&fd); if (sc == RTEMS_SUCCESSFUL) { rv = write(fd, local_buf, write_size); if (rv != write_size) sc = RTEMS_IO_ERROR; } rv = close(fd); if (rv != 0) sc = RTEMS_IO_ERROR; return RTEMS_SUCCESSFUL; } static void altera_cyclone_v_ds1339_initialize(int minor) { rtems_status_code sc = RTEMS_SUCCESSFUL; uint8_t status = 0; /* Check RTC valid */ sc = ds1339_read(DS1339_ADDR_STATUS, &status, sizeof(status)); assert(sc == RTEMS_SUCCESSFUL); if (status & DS1339_STATUS_OSF) { /* RTC has been stopped. Initialise it. */ ds1339_time_t time; uint8_t write = DS1339_CTRL_DEFAULT; sc = ds1339_write(DS1339_ADDR_CTRL, &write, sizeof(write)); assert(sc == RTEMS_SUCCESSFUL); write = DS1339_STATUS_CLEAR; sc = ds1339_write(DS1339_ADDR_STATUS, &write, sizeof(write)); assert(sc == RTEMS_SUCCESSFUL); ds1339_set_time(&time, 0, 0, 0, 1, 1, TOD_BASE_YEAR); sc = ds1339_write(DS1339_ADDR_TIME, &time, sizeof(time)); assert(sc == RTEMS_SUCCESSFUL); } } static int altera_cyclone_v_ds1339_get_time(int minor, rtems_time_of_day* tod) { ds1339_time_t time; rtems_status_code sc = RTEMS_SUCCESSFUL; rtems_time_of_day temp_tod; sc = ds1339_read(DS1339_ADDR_TIME, &time, sizeof(time)); if (sc == RTEMS_SUCCESSFUL) { temp_tod.ticks = 0; temp_tod.second = ds1339_get_seconds(&time); temp_tod.minute = ds1339_get_minutes(&time); temp_tod.hour = ds1339_get_hours(&time); temp_tod.day = ds1339_get_day_of_month(&time); temp_tod.month = ds1339_get_month(&time); temp_tod.year = ds1339_get_year(&time); sc = _TOD_Validate(&temp_tod, TOD_ENABLE_TICKS_VALIDATION) if (sc == RTEMS_SUCCESSFUL) memcpy(tod, &temp_tod, sizeof(temp_tod)); } return -sc; } static int altera_cyclone_v_ds1339_set_time(int minor, const rtems_time_of_day* tod) { ds1339_time_t time; rtems_status_code sc = RTEMS_SUCCESSFUL; ds1339_set_time(&time, tod->second, tod->minute, tod->hour, tod->day, tod->month, tod->year ); sc = ds1339_write(DS1339_ADDR_TIME, &time, sizeof(time)); return -sc; } static bool altera_cyclone_v_ds1339_probe(int minor) { rtems_status_code sc = RTEMS_SUCCESSFUL; uint8_t buf; /* try to read from register address 0x00 */ sc = ds1339_read(0x00, &buf, 1); if (sc != RTEMS_SUCCESSFUL) /* no RTC implemented */ return false; /* try to read from register address 0x20 (not implemented in DS1339) */ sc = ds1339_read(0x20, &buf, 1); if (sc == RTEMS_SUCCESSFUL) /* RTC is not DS1339 */ return false; return true; } /* ******************************* M41ST87 ********************************** */ #define M41ST87_I2C_ADDRESS (0xD0 >> 1) /* 7-bit addressing! */ #define M41ST87_I2C_BUS_DEVICE "/dev/i2c0" #define M41ST87_ADDR_TIME 0x00 #define M41ST87_ADDR_CTRL 0x08 #define M41ST87_CTRL_OUT 0x80 #define M41ST87_CTRL_FT 0x40 #define M41ST87_CTRL_S 0x20 #define M41ST87_CTRL_CAL 0x1F #define M41ST87_ADDR_ALARM_HOUR 0x0C #define M41ST87_BIT_HT 0x40 #define M41ST87_ADDR_FLAGS 0x0F #define M41ST87_FLAG_WDF 0x80 #define M41ST87_FLAG_AF 0x40 #define M41ST87_FLAG_BL 0x10 #define M41ST87_FLAG_OF 0x04 #define M41ST87_FLAG_TB1 0x02 #define M41ST87_FLAG_TB2 0x01 #define M41ST87_ADDR_USER_RAM 0x20 typedef struct { uint8_t sec100; uint8_t seconds; #define M41ST87_BIT_ST 0x80 uint8_t minutes; #define M41ST87_BIT_OFIE 0x80 uint8_t hours; #define M41ST87_BIT_CB1 0x80 #define M41ST87_BIT_CB0 0x40 uint8_t weekday; #define M41ST87_BIT_TR 0x80 #define M41ST87_BIT_THS 0x40 #define M41ST87_BIT_CLRPW1 0x20 #define M41ST87_BIT_CLRPW0 0x10 #define M41ST87_BIT_32KE 0x08 uint8_t day; #define M41ST87_BIT_PFOD 0x80 uint8_t month; uint8_t year; } m41st87_time_t; /* The longest write transmission is writing the time + one address bit */ #define M41ST87_MAX_WRITE_SIZE (sizeof(m41st87_time_t) + 1) /* Functions for converting the fields */ /* static unsigned int m41st87_get_sec100(m41st87_time_t* time) { uint8_t tens = time->sec100 >> 4; uint8_t ones = time->sec100 & 0x0F; return tens * 10 + ones; } */ static unsigned int m41st87_get_seconds(m41st87_time_t* time) { uint8_t tens = (time->seconds >> 4) & 0x07; uint8_t ones = time->seconds & 0x0F; return tens * 10 + ones; } static unsigned int m41st87_get_minutes(m41st87_time_t* time) { uint8_t tens = (time->minutes >> 4) & 0x07; uint8_t ones = time->minutes & 0x0F; return tens * 10 + ones; } static unsigned int m41st87_get_hours(m41st87_time_t* time) { uint8_t tens = (time->hours >> 4) & 0x03; uint8_t ones = time->hours & 0x0F; return tens * 10 + ones; } /* static unsigned int m41st87_get_day_of_week(m41st87_time_t* time) { return time->weekday & 0x07; } */ static unsigned int m41st87_get_day_of_month(m41st87_time_t* time) { uint8_t tens = (time->day >> 4) & 0x03; uint8_t ones = time->day & 0x0F; return tens * 10 + ones; } static unsigned int m41st87_get_month(m41st87_time_t* time) { uint8_t tens = (time->month >> 4) & 0x01; uint8_t ones = time->month & 0x0F; return tens * 10 + ones; } static unsigned int m41st87_get_year(m41st87_time_t* time) { uint8_t century = time->hours >> 6; uint8_t tens = time->year >> 4; uint8_t ones = time->year & 0x0F; return 1900 + century * 100 + tens * 10 + ones; } static void m41st87_set_time(m41st87_time_t* time, unsigned int second, unsigned int minute, unsigned int hour, unsigned int day, unsigned int month, unsigned int year) { unsigned int century; unsigned int tens; unsigned int ones; if (year < 1900) year = 1900; if (year > 2399) year = 2399; century = (year - 1900) / 100; /* Hundreds of seconds is not used, set to 0 */ time->sec100 = 0; tens = second / 10; ones = second % 10; time->seconds = (time->seconds & 0x80) | (tens << 4) | ones; tens = minute / 10; ones = minute % 10; time->minutes = (time->minutes & 0x80) | (tens << 4) | ones; tens = hour / 10; ones = hour % 10; time->hours = (century << 6) | (tens << 4) | ones; /* Weekday is not used. Therefore it can be set to an arbitrary valid value */ time->weekday = (time->weekday & 0xF8) | 1; tens = day / 10; ones = day % 10; time->day = (time->day & 0x80) | (tens << 4) | ones; tens = month / 10; ones = month % 10; time->month = (tens << 4) | ones; tens = (year % 100) / 10; ones = year % 10; time->year = (tens << 4) | ones; } static rtems_status_code m41st87_open_file(int* fd) { int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; *fd = open(M41ST87_I2C_BUS_DEVICE, O_RDWR); if (*fd == -1) sc = RTEMS_IO_ERROR; if (sc == RTEMS_SUCCESSFUL) { rv = ioctl(*fd, I2C_IOC_SET_SLAVE_ADDRESS, M41ST87_I2C_ADDRESS); if (rv == -1) sc = RTEMS_IO_ERROR; } return sc; } /* Read size bytes from m41st87 register address addr to buf. */ static rtems_status_code m41st87_read(uint8_t addr, void* buf, size_t size) { int fd = -1; int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; sc = m41st87_open_file(&fd); if (sc == RTEMS_SUCCESSFUL) { rv = write(fd, &addr, sizeof(addr)); if (rv != sizeof(addr)) sc = RTEMS_IO_ERROR; } if (sc == RTEMS_SUCCESSFUL) { rv = read(fd, buf, size); if (rv != size) sc = RTEMS_IO_ERROR; } rv = close(fd); if (rv != 0) sc = RTEMS_IO_ERROR; return sc; } /* Write size bytes from buf to m41st87 register address addr. */ static rtems_status_code m41st87_write(uint8_t addr, void* buf, size_t size) { int fd = -1; int rv = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; /* The driver never writes many bytes. Therefore it should be less expensive * to reserve the maximum number of bytes that will be written in one go than * use a malloc. */ uint8_t local_buf[M41ST87_MAX_WRITE_SIZE]; int write_size = size + 1; assert(write_size <= M41ST87_MAX_WRITE_SIZE); local_buf[0] = addr; memcpy(&local_buf[1], buf, size); sc = m41st87_open_file(&fd); if (sc == RTEMS_SUCCESSFUL) { rv = write(fd, local_buf, write_size); if (rv != write_size) sc = RTEMS_IO_ERROR; } rv = close(fd); if (rv != 0) sc = RTEMS_IO_ERROR; return RTEMS_SUCCESSFUL; } static void altera_cyclone_v_m41st87_initialize(int minor) { m41st87_time_t time; rtems_status_code sc = RTEMS_SUCCESSFUL; uint8_t value; /* Check RTC valid */ sc = m41st87_read(M41ST87_ADDR_TIME, &time, sizeof(time)); assert(sc == RTEMS_SUCCESSFUL); if (time.seconds & M41ST87_BIT_ST) { /* RTC has been stopped. Reset stop flag. */ time.seconds = 0; /* Initialise RTC. */ m41st87_set_time(&time, 0, 0, 0, 1, 1, TOD_BASE_YEAR); sc = m41st87_write(M41ST87_ADDR_TIME, &time, sizeof(time)); assert(sc == RTEMS_SUCCESSFUL); } /* Reset HT bit */ sc = m41st87_read(M41ST87_ADDR_ALARM_HOUR, &value, 1); assert(sc == RTEMS_SUCCESSFUL); value &= ~M41ST87_BIT_HT; sc = m41st87_write(M41ST87_ADDR_ALARM_HOUR, &value, 1); assert(sc == RTEMS_SUCCESSFUL); } static int altera_cyclone_v_m41st87_get_time(int minor, rtems_time_of_day* tod) { m41st87_time_t time; rtems_status_code sc = RTEMS_SUCCESSFUL; rtems_time_of_day temp_tod; sc = m41st87_read(M41ST87_ADDR_TIME, &time, sizeof(time)); if (sc != RTEMS_SUCCESSFUL) return -sc; temp_tod.ticks = 0; temp_tod.second = m41st87_get_seconds(&time); temp_tod.minute = m41st87_get_minutes(&time); temp_tod.hour = m41st87_get_hours(&time); temp_tod.day = m41st87_get_day_of_month(&time); temp_tod.month = m41st87_get_month(&time); temp_tod.year = m41st87_get_year(&time); sc = _TOD_Validate(&temp_tod); if (sc == RTEMS_SUCCESSFUL) memcpy(tod, &temp_tod, sizeof(temp_tod)); return -sc; } static int altera_cyclone_v_m41st87_set_time(int minor, const rtems_time_of_day* tod) { m41st87_time_t time; rtems_status_code sc = RTEMS_SUCCESSFUL; /* first read to preserve the additional flags */ sc = m41st87_read(M41ST87_ADDR_TIME, &time, sizeof(time)); if (sc != RTEMS_SUCCESSFUL) return -sc; m41st87_set_time(&time, tod->second, tod->minute, tod->hour, tod->day, tod->month, tod->year ); sc = m41st87_write(M41ST87_ADDR_TIME, &time, sizeof(time)); return -sc; } static bool altera_cyclone_v_m41st87_probe(int minor) { rtems_status_code sc = RTEMS_SUCCESSFUL; uint8_t buf; /* try to read from register address 0x00 */ sc = m41st87_read(0x00, &buf, 1); if (sc != RTEMS_SUCCESSFUL) /* no RTC implemented */ return false; /* try to read from register address 0x20 (implemented in M41ST87) */ sc = m41st87_read(0x20, &buf, 1); if (sc != RTEMS_SUCCESSFUL) /* RTC is not M41ST87 */ return false; return true; } /* **************************************** General ********************************** */ const rtc_fns altera_cyclone_v_ds1339_ops = { .deviceInitialize = altera_cyclone_v_ds1339_initialize, .deviceGetTime = altera_cyclone_v_ds1339_get_time, .deviceSetTime = altera_cyclone_v_ds1339_set_time }; const rtc_fns altera_cyclone_v_m41st87_ops = { .deviceInitialize = altera_cyclone_v_m41st87_initialize, .deviceGetTime = altera_cyclone_v_m41st87_get_time, .deviceSetTime = altera_cyclone_v_m41st87_set_time }; size_t RTC_Count = ALTERA_CYCLONE_V_RTC_NUMBER; rtc_tbl RTC_Table[ALTERA_CYCLONE_V_RTC_NUMBER] = { { .sDeviceName = "/dev/rtc", .deviceType = RTC_CUSTOM, .pDeviceFns = &altera_cyclone_v_ds1339_ops, .deviceProbe = altera_cyclone_v_ds1339_probe, .pDeviceParams = NULL, .ulCtrlPort1 = 0, .ulDataPort = 0, .getRegister = NULL, .setRegister = NULL }, { .sDeviceName = "/dev/rtc", .deviceType = RTC_CUSTOM, .pDeviceFns = &altera_cyclone_v_m41st87_ops, .deviceProbe = altera_cyclone_v_m41st87_probe, .pDeviceParams = NULL, .ulCtrlPort1 = 0, .ulDataPort = 0, .getRegister = NULL, .setRegister = NULL } };