/* * pcf8563_i2c_rtc.c - example of accessing a PCF8563 via the BSC0 (I2C) peripheral on a BCM2835 (Raspberry Pi) * * Copyright 2012 Kevin Sangeelee. * Released as GPLv2, see * * This is intended as an example of using Raspberry Pi hardware registers to drive an RTC chip. Use at your own risk or * not at all. As far as possible, I've omitted anything that doesn't relate to the RTC or the Raspi registers. There are more * conventional ways of doing this using kernel drivers, though they're harder to follow what's happening in hardware. */ #include #include #include #include #define IOBASE 0x20000000 #define BSC0_BASE (IOBASE + 0x205000) #define GPIO_BASE (IOBASE + 0x200000) #define BSC0_C *(bsc0.addr + 0x00) #define BSC0_S *(bsc0.addr + 0x01) #define BSC0_DLEN *(bsc0.addr + 0x02) #define BSC0_A *(bsc0.addr + 0x03) #define BSC0_FIFO *(bsc0.addr + 0x04) #define BSC_C_I2CEN (1 << 15) #define BSC_C_INTR (1 << 10) #define BSC_C_INTT (1 << 9) #define BSC_C_INTD (1 << 8) #define BSC_C_ST (1 << 7) #define BSC_C_CLEAR (1 << 4) #define BSC_C_READ 1 #define START_READ BSC_C_I2CEN|BSC_C_ST|BSC_C_CLEAR|BSC_C_READ #define START_WRITE BSC_C_I2CEN|BSC_C_ST #define BSC_S_CLKT (1 << 9) #define BSC_S_ERR (1 << 8) #define BSC_S_RXF (1 << 7) #define BSC_S_TXE (1 << 6) #define BSC_S_RXD (1 << 5) #define BSC_S_TXD (1 << 4) #define BSC_S_RXR (1 << 3) #define BSC_S_TXW (1 << 2) #define BSC_S_DONE (1 << 1) #define BSC_S_TA 1 #define CLEAR_STATUS BSC_S_CLKT|BSC_S_ERR|BSC_S_DONE #define PAGESIZE 4096 #define BLOCK_SIZE 4096 struct bcm2835_peripheral { unsigned long addr_p; int mem_fd; void *map; volatile unsigned int *addr; }; struct bcm2835_peripheral gpio = {GPIO_BASE}; struct bcm2835_peripheral bsc0 = {BSC0_BASE}; // Some forward declarations... void dump_bsc_status(); int map_peripheral(struct bcm2835_peripheral *p); void unmap_peripheral(struct bcm2835_peripheral *p); int systohc = 0; int hctosys = 0; struct tm t; time_t now; // BCD helper functions only apply to BCD 0-99 (one byte) values unsigned int bcdtod(unsigned int bcd) { return ((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f); } unsigned int dtobcd(unsigned int d) { return ((d / 10) << 4) + (d % 10); } // Function to wait for the I2C transaction to complete void wait_i2c_done() { //Wait till done, let's use a timeout just in case int timeout = 50; while((!((BSC0_S) & BSC_S_DONE)) && --timeout) { usleep(1000); } if(timeout == 0) printf("wait_i2c_done() timeout. Something went wrong.\n"); } //////////////// // main() //////////////// int main(int argc, char *argv[]) { if(argc == 2) { if(!strcmp(argv[1], "-w")) systohc = 1; if(!strcmp(argv[1], "-s")) hctosys = 1; } if(map_peripheral(&gpio) == -1) { printf("Failed to map the physical GPIO registers into the virtual memory space.\n"); return -1; } if(map_peripheral(&bsc0) == -1) { printf("Failed to map the physical BSC0 (I2C) registers into the virtual memory space.\n"); return -1; } /* BSC0 is on GPIO 0 & 1 */ *gpio.addr &= ~0x3f; // Mask out bits 0-5 of FSEL0 (i.e. force to zero) *gpio.addr |= 0x24; // Set bits 0-5 of FSEL0 to binary '100100' // I2C Device Address 0x51 (hardwired into the RTC chip) BSC0_A = 0x51; if(systohc) { printf("Setting RTC from system clock\n"); now = time(NULL); gmtime_r(&now, &t); // explode time_t (now) into an struct tm ////// // Write Operation to set the time (writing 15 of the 16 RTC registers to // also reset status, alarm, and timer settings). ////// BSC0_DLEN = 16; BSC0_FIFO = 0; // Addr 0 BSC0_FIFO = 0; // control1 BSC0_FIFO = 0; // control2 BSC0_FIFO = dtobcd(t.tm_sec); // seconds BSC0_FIFO = dtobcd(t.tm_min); // mins BSC0_FIFO = dtobcd(t.tm_hour); // hours BSC0_FIFO = dtobcd(t.tm_mday); // days BSC0_FIFO = dtobcd(t.tm_wday); // weekdays (sun 0) BSC0_FIFO = dtobcd(t.tm_mon + 1); // months 0-11 --> 1-12 BSC0_FIFO = dtobcd(t.tm_year - 100); // years BSC0_FIFO = 0x0; // alarm min BSC0_FIFO = 0x0; // alarm hour BSC0_FIFO = 0x0; // alarm day BSC0_FIFO = 0x0; // alarm weekday BSC0_FIFO = 0x0; // CLKOUT control BSC0_FIFO = 0x0; // timer control BSC0_S = CLEAR_STATUS; // Reset status bits (see #define) BSC0_C = START_WRITE; // Start Write (see #define) wait_i2c_done(); } ////// // Write operation to restart the PCF8563 register at index 2 ('secs' field) ////// BSC0_DLEN = 1; // one byte BSC0_FIFO = 2; // value 2 BSC0_S = CLEAR_STATUS; // Reset status bits (see #define) BSC0_C = START_WRITE; // Start Write (see #define) wait_i2c_done(); ////// // Start Read of RTC chip's time ////// BSC0_DLEN = 7; BSC0_S = CLEAR_STATUS; // Reset status bits (see #define) BSC0_C = START_READ; // Start Read after clearing FIFO (see #define) wait_i2c_done(); // Store the values read in the tm structure, after masking unimplemented bits. t.tm_sec = bcdtod(BSC0_FIFO & 0x7f); t.tm_min = bcdtod(BSC0_FIFO & 0x7f); t.tm_hour = bcdtod(BSC0_FIFO & 0x3f); t.tm_mday = bcdtod(BSC0_FIFO & 0x3f); t.tm_wday = bcdtod(BSC0_FIFO & 0x07); t.tm_mon = bcdtod(BSC0_FIFO & 0x1f) - 1; // 1-12 --> 0-11 t.tm_year = bcdtod(BSC0_FIFO) + 100; printf("%02d:%02d:%02d %02d/%02d/%02d (UTC on PCF8563)\n", t.tm_hour,t.tm_min,t.tm_sec, t.tm_mday,t.tm_mon + 1,t.tm_year - 100); if(hctosys) { printf("Setting system clock from RTC\n"); now = timegm(&t); stime(&now); } unmap_peripheral(&gpio); unmap_peripheral(&bsc0); // Done! } // Exposes the physical address defined in the passed structure using mmap on /dev/mem int map_peripheral(struct bcm2835_peripheral *p) { // Open /dev/mem if ((p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { printf("Failed to open /dev/mem, try checking permissions.\n"); return -1; } p->map = mmap( NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, p->mem_fd, // File descriptor to physical memory virtual file '/dev/mem' p->addr_p // Address in physical map that we want this memory block to expose ); if (p->map == MAP_FAILED) { perror("mmap"); return -1; } p->addr = (volatile unsigned int *)p->map; return 0; } void unmap_peripheral(struct bcm2835_peripheral *p) { munmap(p->map, BLOCK_SIZE); close(p->mem_fd); } void dump_bsc_status() { unsigned int s = BSC0_S; printf("BSC0_S: ERR=%d RXF=%d TXE=%d RXD=%d TXD=%d RXR=%d TXW=%d DONE=%d TA=%d\n", (s & BSC_S_ERR) != 0, (s & BSC_S_RXF) != 0, (s & BSC_S_TXE) != 0, (s & BSC_S_RXD) != 0, (s & BSC_S_TXD) != 0, (s & BSC_S_RXR) != 0, (s & BSC_S_TXW) != 0, (s & BSC_S_DONE) != 0, (s & BSC_S_TA) != 0 ); }