@@ 0,0 1,280 @@
+/*
+ * Copyright (c) 2024 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * 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);
+}