# HG changeset patch # User Josef 'Jeff' Sipek # Date 1721043572 14400 # Mon Jul 15 07:39:32 2024 -0400 # Node ID 17c11895475e421c45b464566f45b7607368f973 # Parent 231f3f42e5564c7c67d0f443d999b1c7758db05d devices: add code to access MCP7940N RTC Signed-off-by: Josef 'Jeff' Sipek diff --git a/devices/i2c/mcp7940n.c b/devices/i2c/mcp7940n.c new file mode 100644 --- /dev/null +++ b/devices/i2c/mcp7940n.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2024 Josef 'Jeff' Sipek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "../../arch.h" +#include "mcp7940n.h" + +static inline uint8_t div10(uint8_t v) +{ + uint8_t out; + + for (out = 0; v >= 10; out++) + v -= 10; + + return out; +} + +static inline uint8_t mod10(uint8_t v) +{ + return v - div10(v) * 10; +} + +#define BCD2BIN(val, umask, ushift, lmask, lshift) \ + ((((val) & umask) >> ushift) * 10 + \ + (((val) & lmask) >> lshift)) +#define BIN2BCD(val, ushift, lshift) \ + ((div10(val) << ushift) | \ + (mod10(val) << lshift)) + +/* + * Returns: + * -1 = error + * 0 = success, started RTC + * +1 = success, RTC already running + */ +int mcp7940n_init(const struct tm *tm) +{ + struct mcp7940n_timekeeping_regs tmp; + + if (!mcp7940n_read(offsetof(struct mcp7940n, timekeeping), &tmp, + sizeof(tmp))) + return -1; + + if (tmp.sec & MCP7940N_SEC_ST) + return +1; /* already started, don't disturb the time */ + + if (!mcp7940n_settime(tm)) + return -1; + + return 0; +} + +/* + * write a number of bytes to the specified RTC register address + * + * Example I2C traffic: + * + * [ 0xde 0x00 0x80 0x41 0x00 0x02 0x25 0x12 ... ] + * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | | | `- stop condition + * | | | `----`----`----`----`----`- data written + * | | `- register address + * | `- device address + * `- start condition + */ +bool mcp7940n_write(uint8_t addr, const void *_buf, size_t len) +{ + const uint8_t *buf = _buf; + size_t i; + + i2c_start(); + if (!i2c_sync(I2C_STATUS_START)) + goto err; + + i2c_tx_byte(MCP7940N_ADDR_W); + if (!i2c_sync(I2C_STATUS_MT_SLA_ACK)) + goto err; + + i2c_tx_byte(addr); + if (!i2c_sync(I2C_STATUS_MT_DATA_ACK)) + goto err; + + for (i = 0; i < len; i++) { + i2c_tx_byte(buf[i]); + if (!i2c_sync(I2C_STATUS_MT_DATA_ACK)) + goto err; + } + + i2c_stop(); + + return true; + +err: + i2c_stop(); + i2c_stop(); + + return false; +} + +/* + * read a number of bytes from the specified RTC register address + * + * Example I2C traffic: + * + * [ 0xde 0x00 [ 0xdf 0xAA 0xBB 0xCC ... ] + * ^ ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | | `- stop condition + * | | | | | `----`----`- read data + * | | | | `- device address + * | | | `- repeated start condition + * | | `- register address + * | `- device address + * `- start condition + */ +bool mcp7940n_read(uint8_t addr, void *_buf, size_t len) +{ + uint8_t *buf = _buf; + size_t i; + + i2c_start(); + if (!i2c_sync(I2C_STATUS_START)) + goto err; + + i2c_tx_byte(MCP7940N_ADDR_W); + if (!i2c_sync(I2C_STATUS_MT_SLA_ACK)) + goto err; + + i2c_tx_byte(addr); + if (!i2c_sync(I2C_STATUS_MT_DATA_ACK)) + goto err; + + i2c_start(); + if (!i2c_sync(I2C_STATUS_REP_START)) + goto err; + + i2c_tx_byte(MCP7940N_ADDR_R); + if (!i2c_sync(I2C_STATUS_MR_SLA_ACK)) + goto err; + + for (i = 0; i < len; i++) + buf[i] = i2c_rx_byte((i + 1) == len, NULL); + + i2c_stop(); + + return true; + +err: + i2c_stop(); + i2c_stop(); + + return false; +} + +bool mcp7940n_gettime(struct tm *tm) +{ + struct mcp7940n_timekeeping_regs tmp; + + if (!mcp7940n_read(offsetof(struct mcp7940n, timekeeping), &tmp, + sizeof(tmp))) + return false; + + if (!(tmp.sec & MCP7940N_SEC_ST) || + !(tmp.wkday & MCP7940N_WKDAY_OSCRUN)) + return false; + + tm->sec = BCD2BIN(tmp.sec, 0x70, 4, 0x0f, 0); + tm->min = BCD2BIN(tmp.min, 0x70, 4, 0x0f, 0); + tm->hour = BCD2BIN(tmp.hour, 0x10, 4, 0x0f, 0); + if ((tmp.hour & MCP7940N_HOUR_FMT) == MCP7940N_HOUR_FMT_12H) + tm->hour += (tmp.hour & 0x20) ? 12 /* pm */ : 0 /* am */; + else + tm->hour += (tmp.hour & 0x20) ? 20 : 0; + tm->wkday = BCD2BIN(tmp.wkday, 0, 0, 0x07, 0); + tm->day = BCD2BIN(tmp.date, 0x30, 4, 0x0f, 0); + tm->month = BCD2BIN(tmp.month, 0x10, 4, 0x0f, 0); + tm->year = 2000 + BCD2BIN(tmp.year, 0xf0, 4, 0x0f, 0); + + return true; +} + +bool mcp7940n_settime(const struct tm *tm) +{ + struct mcp7940n_timekeeping_regs tmp = { }; + + /* stop the oscillator */ + tmp.sec = 0; + if (!mcp7940n_write(offsetof(struct mcp7940n, timekeeping.sec), + &tmp.sec, sizeof(tmp.sec))) + return false; + + tmp.sec = BIN2BCD(tm->sec, 4, 0) & 0x7f; + tmp.min = BIN2BCD(tm->min, 4, 0) & 0x7f; + tmp.hour = MCP7940N_HOUR_FMT_24H | (BIN2BCD(tm->hour, 4, 0) & 0x3f); + tmp.wkday = MCP7940N_WKDAY_VBATEN | (BIN2BCD(0 /* FIXME */, 8, 0) & 0x7); + tmp.date = BIN2BCD(tm->day, 4, 0) & 0x3f; + tmp.month = BIN2BCD(tm->month, 4, 0) & 0x1f; + tmp.year = BIN2BCD(tm->year - 2000, 4, 0); + + if (!mcp7940n_write(offsetof(struct mcp7940n, timekeeping), &tmp, + sizeof(tmp))) + return false; + + /* start the oscillator */ + tmp.sec |= MCP7940N_SEC_ST; + if (!mcp7940n_write(offsetof(struct mcp7940n, timekeeping.sec), + &tmp.sec, sizeof(tmp.sec))) + return false; + + /* TODO: check OSCRUN in RTCWKDAY */ + + return true; +} + +bool mcp7940n_gettrim(int8_t *v) +{ + uint8_t tmp; + + if (!mcp7940n_read(offsetof(struct mcp7940n, timekeeping.trim), &tmp, + sizeof(tmp))) + return false; + + if (tmp & 0x80) + *v = -((int8_t) (tmp & 0x7f)); + else + *v = tmp; + + return true; +} + +bool mcp7940n_settrim(int8_t v) +{ + uint8_t tmp; + + if (v < 0) + tmp = 0x80 | ((uint8_t) -v); + else + tmp = v; + + return mcp7940n_write(offsetof(struct mcp7940n, timekeeping.trim), + &tmp, sizeof(tmp)); +} + +bool mcp7940n_adjtrim(int8_t delta) +{ + int16_t tmp; + int8_t v; + + if (!mcp7940n_gettrim(&v)) + return false; + + tmp = v + delta; + + /* clamp the new trim value */ + if (tmp > 127) + v = 127; + else if (tmp < -127) + v = - -127; + else + v = tmp; + + return mcp7940n_settrim(v); +} diff --git a/devices/i2c/mcp7940n.h b/devices/i2c/mcp7940n.h --- a/devices/i2c/mcp7940n.h +++ b/devices/i2c/mcp7940n.h @@ -90,6 +90,15 @@ }; STATIC_ASSERT(sizeof(struct mcp7940n) == 32); +extern int mcp7940n_init(const struct tm *tm); +extern bool mcp7940n_write(uint8_t addr, const void *_buf, size_t len); +extern bool mcp7940n_read(uint8_t addr, void *_buf, size_t len); +extern bool mcp7940n_gettime(struct tm *tm); +extern bool mcp7940n_settime(const struct tm *tm); +extern bool mcp7940n_gettrim(int8_t *v); +extern bool mcp7940n_settrim(int8_t v); +extern bool mcp7940n_adjtrim(int8_t delta); + #endif #endif