--- /dev/null
+#include <minix/ds.h>
+#include <minix/drivers.h>
+#include <minix/i2c.h>
+#include <minix/i2cdriver.h>
+#include <minix/log.h>
+
+#include <time.h>
+
+#include "tps65950.h"
+#include "rtc.h"
+
+/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
+static struct log log = {
+ .name = "tps65950.rtc",
+ .log_level = LEVEL_INFO,
+ .log_func = default_log
+};
+
+static int bcd_to_dec(int n);
+static int dec_to_bcd(int n);
+
+int
+rtc_init(void)
+{
+ int r;
+ uint8_t val;
+ struct tm t;
+
+ r = reg_set(ID4, RTC_CTRL_REG, (1 << STOP_RTC_BIT));
+ if (r != OK) {
+ log_warn(&log, "Failed to start RTC\n");
+ return -1;
+ }
+
+ r = reg_read(ID4, RTC_STATUS_REG, &val);
+ if (r != OK) {
+ log_warn(&log, "Failed to read RTC_STATUS_REG\n");
+ return -1;
+ }
+
+ if ((val & (1 << RUN_BIT)) != (1 << RUN_BIT)) {
+ log_warn(&log, "RTC did not start. Bad MSECURE?\n");
+ return -1;
+ }
+
+ log_debug(&log, "RTC Started\n");
+
+ return OK;
+}
+
+int
+rtc_get_time(struct tm *t, int flags)
+{
+ int r;
+ uint8_t val;
+
+ memset(t, '\0', sizeof(struct tm));
+
+ /* Write GET_TIME_BIT to RTC_CTRL_REG to latch the RTC values into
+ * the RTC registers. This is required before each read.
+ */
+ r = reg_set(ID4, RTC_CTRL_REG, (1 << GET_TIME_BIT));
+ if (r != OK) {
+ return -1;
+ }
+
+ /* Read and Convert BCD to binary (default RTC mode). */
+
+ /* Seconds - 0 to 59 */
+ r = reg_read(ID4, SECONDS_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_sec = bcd_to_dec(val & 0x7f);
+
+ /* Minutes - 0 to 59 */
+ r = reg_read(ID4, MINUTES_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_min = bcd_to_dec(val & 0x7f);
+
+ /* Hours - 0 to 23 */
+ r = reg_read(ID4, HOURS_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_hour = bcd_to_dec(val & 0x3f);
+
+ /* Days - 1 to 31 */
+ r = reg_read(ID4, DAYS_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_mday = bcd_to_dec(val & 0x3f);
+
+ /* Months - Jan=1 to Dec=12 */
+ r = reg_read(ID4, MONTHS_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_mon = bcd_to_dec(val & 0x1f) - 1;
+
+ /* Years - last 2 digits of year */
+ r = reg_read(ID4, YEARS_REG, &val);
+ if (r != OK) {
+ return -1;
+ }
+ t->tm_year = bcd_to_dec(val & 0x1f) + 100;
+
+ if (t->tm_year == 100) {
+ /* Cold start - no date/time set - default to 2013-01-01 */
+ t->tm_sec = 0;
+ t->tm_min = 0;
+ t->tm_hour = 0;
+ t->tm_mday = 1;
+ t->tm_mon = 0;
+ t->tm_year = 113;
+
+ rtc_set_time(t, RTCDEV_NOFLAGS);
+ }
+
+ return OK;
+}
+
+int
+rtc_set_time(struct tm *t, int flags)
+{
+ int r;
+
+ /* Write the date/time to the RTC registers. */
+ r = reg_write(ID4, SECONDS_REG, (dec_to_bcd(t->tm_sec) & 0x7f));
+ if (r != OK) {
+ return -1;
+ }
+
+ r = reg_write(ID4, MINUTES_REG, (dec_to_bcd(t->tm_min) & 0x7f));
+ if (r != OK) {
+ return -1;
+ }
+
+ r = reg_write(ID4, HOURS_REG, (dec_to_bcd(t->tm_hour) & 0x3f));
+ if (r != OK) {
+ return -1;
+ }
+
+ r = reg_write(ID4, DAYS_REG, (dec_to_bcd(t->tm_mday) & 0x3f));
+ if (r != OK) {
+ return -1;
+ }
+
+ r = reg_write(ID4, MONTHS_REG, (dec_to_bcd(t->tm_mon + 1) & 0x1f));
+ if (r != OK) {
+ return -1;
+ }
+
+ r = reg_write(ID4, YEARS_REG, (dec_to_bcd(t->tm_year % 100) & 0xff));
+ if (r != OK) {
+ return -1;
+ }
+
+ return OK;
+}
+
+int
+rtc_exit(void)
+{
+ return OK;
+}
+
+static int
+bcd_to_dec(int n)
+{
+ return ((n >> 4) & 0x0F) * 10 + (n & 0x0F);
+}
+
+static int
+dec_to_bcd(int n)
+{
+ return ((n / 10) << 4) | (n % 10);
+}
--- /dev/null
+#include <minix/ds.h>
+#include <minix/drivers.h>
+#include <minix/i2c.h>
+#include <minix/i2cdriver.h>
+#include <minix/log.h>
+#include <minix/safecopies.h>
+
+#include "tps65950.h"
+#include "rtc.h"
+
+/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
+static struct log log = {
+ .name = "tps65950",
+ .log_level = LEVEL_INFO,
+ .log_func = default_log
+};
+
+/* TPS65950 doesn't support configuring the addresses, so there is only 1
+ * configuration possible. The chip does have multiple addresses (0x48,
+ * 0x49, 0x4a, 0x4b), but because they're all fixed, we only have the
+ * user pass the base address as a sanity check.
+ */
+static i2c_addr_t valid_addrs[2] = {
+ 0x48, 0x00
+};
+
+/* the bus that this device is on (counting starting at 1) */
+static uint32_t bus;
+
+/* endpoint for the driver for the bus itself. */
+static endpoint_t bus_endpoint;
+
+/* slave addresses of the device */
+#define NADDRESSES 4
+static i2c_addr_t addresses[NADDRESSES] = {
+ 0x48, 0x49, 0x4a, 0x4b
+};
+
+/* local functions */
+static int check_revision(void);
+
+/* SEF related functions */
+static void sef_local_startup(void);
+static int sef_cb_lu_state_save(int);
+static int lu_state_restore(void);
+static int sef_cb_init(int type, sef_init_info_t * info);
+
+/* functions for transfering struct tm to/from this driver and calling proc. */
+static int fetch_t(endpoint_t ep, cp_grant_id_t gid, struct tm *t);
+static int store_t(endpoint_t ep, cp_grant_id_t gid, struct tm *t);
+
+int
+reg_read(uint8_t id, uint8_t reg, uint8_t * val)
+{
+ int r;
+ minix_i2c_ioctl_exec_t ioctl_exec;
+
+ if (id < 0 || id >= NADDRESSES) {
+ log_warn(&log, "id parameter out of range.\n");
+ return EINVAL;
+ }
+
+ if (val == NULL) {
+ log_warn(&log, "Read called with NULL pointer\n");
+ return EINVAL;
+ }
+
+ memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+ /* Read from chip */
+ ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
+ ioctl_exec.iie_addr = addresses[id];
+
+ /* write the register address */
+ ioctl_exec.iie_cmd[0] = reg;
+ ioctl_exec.iie_cmdlen = 1;
+
+ /* read 1 byte */
+ ioctl_exec.iie_buflen = 1;
+
+ r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
+ if (r != OK) {
+ log_warn(&log, "reg_read() failed (r=%d)\n", r);
+ return -1;
+ }
+
+ *val = ioctl_exec.iie_buf[0];
+
+ log_trace(&log, "Read 0x%x from reg 0x%x", *val, reg);
+
+ return OK;
+}
+
+int
+reg_write(uint8_t id, uint8_t reg, uint8_t val)
+{
+ int r;
+ minix_i2c_ioctl_exec_t ioctl_exec;
+
+ if (id < 0 || id >= NADDRESSES) {
+ log_warn(&log, "id parameter out of range.\n");
+ return EINVAL;
+ }
+
+ memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
+
+ /* Write to chip */
+ ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
+ ioctl_exec.iie_addr = addresses[id];
+
+ /* write the register address and value */
+ ioctl_exec.iie_buf[0] = reg;
+ ioctl_exec.iie_buf[1] = val;
+ ioctl_exec.iie_buflen = 2;
+
+ r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
+ if (r != OK) {
+ log_warn(&log, "reg_write() failed (r=%d)\n", r);
+ return -1;
+ }
+
+ log_trace(&log, "Successfully wrote 0x%x to reg 0x%x\n", val, reg);
+
+ return OK;
+}
+
+int
+reg_set(uint8_t id, uint8_t reg, uint8_t mask)
+{
+ int r;
+ uint8_t val;
+
+ r = reg_read(id, reg, &val);
+ if (r != OK) {
+ return -1;
+ }
+
+ val |= mask;
+
+ r = reg_write(id, reg, val);
+ if (r != OK) {
+ return -1;
+ }
+
+ return OK;
+}
+
+int
+reg_clear(uint8_t id, uint8_t reg, uint8_t mask)
+{
+ int r;
+ uint8_t val;
+
+ r = reg_read(id, reg, &val);
+ if (r != OK) {
+ return -1;
+ }
+
+ val &= ~mask;
+
+ r = reg_write(id, reg, val);
+ if (r != OK) {
+ return -1;
+ }
+
+ return OK;
+}
+
+static int
+fetch_t(endpoint_t ep, cp_grant_id_t gid, struct tm *t)
+{
+ int r;
+
+ r = sys_safecopyfrom(ep, gid, (vir_bytes) 0, (vir_bytes) t,
+ sizeof(struct tm));
+ if (r != OK) {
+ log_warn(&log, "sys_safecopyfrom() failed (r=%d)\n", r);
+ return r;
+ }
+
+ return OK;
+}
+
+static int
+store_t(endpoint_t ep, cp_grant_id_t gid, struct tm *t)
+{
+ int r;
+
+ r = sys_safecopyto(ep, gid, (vir_bytes) 0, (vir_bytes) t,
+ sizeof(struct tm));
+ if (r != OK) {
+ log_warn(&log, "sys_safecopyto() failed (r=%d)\n", r);
+ return r;
+ }
+
+ return OK;
+}
+
+static int
+check_revision(void)
+{
+ int r;
+ uint32_t idcode;
+ uint8_t idcode_7_0, idcode_15_8, idcode_23_16, idcode_31_24;
+
+ /* need to write a special code to unlock read protect on IDCODE */
+ r = reg_write(ID2, UNLOCK_TEST_REG, UNLOCK_TEST_CODE);
+ if (r != OK) {
+ log_warn(&log, "Failed to write unlock code to UNLOCK_TEST\n");
+ return -1;
+ }
+
+ /*
+ * read each part of the IDCODE
+ */
+ r = reg_read(ID2, IDCODE_7_0_REG, &idcode_7_0);
+ if (r != OK) {
+ log_warn(&log, "Failed to read IDCODE part 1\n");
+ }
+
+ r = reg_read(ID2, IDCODE_15_8_REG, &idcode_15_8);
+ if (r != OK) {
+ log_warn(&log, "Failed to read IDCODE part 2\n");
+ }
+
+ r = reg_read(ID2, IDCODE_23_16_REG, &idcode_23_16);
+ if (r != OK) {
+ log_warn(&log, "Failed to read IDCODE part 3\n");
+ }
+
+ r = reg_read(ID2, IDCODE_31_24_REG, &idcode_31_24);
+ if (r != OK) {
+ log_warn(&log, "Failed to read IDCODE part 4\n");
+ }
+
+ /* combine the parts to get the full IDCODE */
+ idcode =
+ ((idcode_31_24 << 24) | (idcode_23_16 << 16) | (idcode_15_8 << 8) |
+ (idcode_7_0 << 0));
+
+ log_debug(&log, "IDCODE = 0x%x\n", idcode);
+ switch (idcode) {
+ case IDCODE_REV_1_0:
+ log_debug(&log, "TPS65950 rev 1.0\n");
+ break;
+ case IDCODE_REV_1_1:
+ log_debug(&log, "TPS65950 rev 1.1\n");
+ break;
+ case IDCODE_REV_1_2:
+ log_debug(&log, "TPS65950 rev 1.2\n");
+ break;
+ default:
+ log_warn(&log, "Unexpected IDCODE: 0x%x\n", idcode);
+ return -1;
+ }
+
+ return OK;
+}
+
+static int
+sef_cb_lu_state_save(int UNUSED(state))
+{
+ /* The addresses are fixed/non-configurable so bus is the only state */
+ ds_publish_u32("bus", bus, DSF_OVERWRITE);
+ return OK;
+}
+
+static int
+lu_state_restore(void)
+{
+ /* Restore the state. */
+ u32_t value;
+
+ ds_retrieve_u32("bus", &value);
+ ds_delete_u32("bus");
+ bus = (int) value;
+
+ return OK;
+}
+
+static int
+sef_cb_init(int type, sef_init_info_t * UNUSED(info))
+{
+ int r, i;
+
+ if (type == SEF_INIT_LU) {
+ /* Restore the state. */
+ lu_state_restore();
+ }
+
+ /* look-up the endpoint for the bus driver */
+ bus_endpoint = i2cdriver_bus_endpoint(bus);
+ if (bus_endpoint == 0) {
+ log_warn(&log, "Couldn't find bus driver.\n");
+ return EXIT_FAILURE;
+ }
+
+ for (i = 0; i < NADDRESSES; i++) {
+
+ /* claim the device */
+ r = i2cdriver_reserve_device(bus_endpoint, addresses[i]);
+ if (r != OK) {
+ log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
+ addresses[i], r);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* check that the chip / rev is reasonable */
+ r = check_revision();
+ if (r != OK) {
+ /* prevent user from using the driver with a different chip */
+ log_warn(&log, "Bad IDCODE\n");
+ return EXIT_FAILURE;
+ }
+
+ r = rtc_init();
+ if (r != OK) {
+ log_warn(&log, "RTC Start-up Failed\n");
+ return EXIT_FAILURE;
+ }
+
+ if (type != SEF_INIT_LU) {
+
+ /* sign up for updates about the i2c bus going down/up */
+ r = i2cdriver_subscribe_bus_updates(bus);
+ if (r != OK) {
+ log_warn(&log, "Couldn't subscribe to bus updates\n");
+ return EXIT_FAILURE;
+ }
+
+ i2cdriver_announce(bus);
+ log_debug(&log, "announced\n");
+ }
+
+ return OK;
+}
+
+static void
+sef_local_startup(void)
+{
+ /*
+ * Register init callbacks. Use the same function for all event types
+ */
+ sef_setcb_init_fresh(sef_cb_init);
+ sef_setcb_init_lu(sef_cb_init);
+ sef_setcb_init_restart(sef_cb_init);
+
+ /*
+ * Register live update callbacks.
+ */
+ /* Agree to update immediately when LU is requested in a valid state. */
+ sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
+ /* Support live update starting from any standard state. */
+ sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
+ /* Register a custom routine to save the state. */
+ sef_setcb_lu_state_save(sef_cb_lu_state_save);
+
+ /* Let SEF perform startup. */
+ sef_startup();
+}
+
+int
+main(int argc, char *argv[])
+{
+ int r, i;
+ struct tm t;
+ endpoint_t user, caller;
+ message m;
+ int ipc_status, reply_status;
+
+ env_setargs(argc, argv);
+
+ r = i2cdriver_env_parse(&bus, &addresses[0], valid_addrs);
+ if (r < 0) {
+ log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
+ log_warn(&log, "Example -args 'bus=1 address=0x48'\n");
+ return EXIT_FAILURE;
+ } else if (r > 0) {
+ log_warn(&log,
+ "Invalid slave address for device, expecting 0x48\n");
+ return EXIT_FAILURE;
+ }
+
+ sef_local_startup();
+
+ while (TRUE) {
+
+ /* Receive Message */
+ r = sef_receive_status(ANY, &m, &ipc_status);
+ if (r != OK) {
+ log_warn(&log, "sef_receive_status() failed\n");
+ continue;
+ }
+
+ if (is_ipc_notify(ipc_status)) {
+
+ if (m.m_source == DS_PROC_NR) {
+ for (i = 0; i < NADDRESSES; i++) {
+ /* changed state, update endpoint */
+ i2cdriver_handle_bus_update
+ (&bus_endpoint, bus, addresses[i]);
+ }
+ }
+
+ /* Do not reply to notifications. */
+ continue;
+ }
+
+ caller = m.m_source;
+
+ log_debug(&log, "Got message 0x%x from 0x%x\n", m.m_type,
+ caller);
+
+ switch (m.m_type) {
+ case RTCDEV_GET_TIME_G:
+ /* Any user can read the time */
+ reply_status = rtc_get_time(&t, m.RTCDEV_FLAGS);
+ if (reply_status != OK) {
+ break;
+ }
+
+ /* write results back to calling process */
+ reply_status =
+ store_t(caller, (cp_grant_id_t) m.RTCDEV_GRANT,
+ &t);
+ break;
+
+ case RTCDEV_SET_TIME_G:
+ /* Only super user is allowed to set the time */
+ if (getnuid(caller) == SUPER_USER) {
+ /* read time from calling process */
+ reply_status =
+ fetch_t(caller,
+ (cp_grant_id_t) m.RTCDEV_GRANT, &t);
+ if (reply_status != OK) {
+ break;
+ }
+
+ reply_status =
+ rtc_set_time(&t, m.RTCDEV_FLAGS);
+ } else {
+ reply_status = EPERM;
+ }
+ break;
+
+ case RTCDEV_PWR_OFF:
+ reply_status = ENOSYS;
+ break;
+
+ default:
+ /* Unrecognized call */
+ reply_status = EINVAL;
+ break;
+ }
+
+ /* Send Reply */
+ m.m_type = RTCDEV_REPLY;
+ m.RTCDEV_STATUS = reply_status;
+
+ log_debug(&log, "Sending Reply");
+
+ r = sendnb(caller, &m);
+ if (r != OK) {
+ log_warn(&log, "sendnb() failed\n");
+ continue;
+ }
+ }
+
+ rtc_exit();
+
+ return 0;
+}