makes booting on qemu beaglexm a lot prettier. Change-Id: I45d0383abbf0d21227a95f538ed78ca6c1ed7871
		
			
				
	
	
		
			364 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#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. */
 | 
						|
endpoint_t bus_endpoint;
 | 
						|
 | 
						|
/* slave addresses of the device */
 | 
						|
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);
 | 
						|
 | 
						|
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 = i2creg_write8(bus_endpoint, addresses[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 = i2creg_read8(bus_endpoint, addresses[ID2], IDCODE_7_0_REG,
 | 
						|
	    &idcode_7_0);
 | 
						|
	if (r != OK) {
 | 
						|
		log_warn(&log, "Failed to read IDCODE part 1\n");
 | 
						|
	}
 | 
						|
 | 
						|
	r = i2creg_read8(bus_endpoint, addresses[ID2], IDCODE_15_8_REG,
 | 
						|
	    &idcode_15_8);
 | 
						|
	if (r != OK) {
 | 
						|
		log_warn(&log, "Failed to read IDCODE part 2\n");
 | 
						|
	}
 | 
						|
 | 
						|
	r = i2creg_read8(bus_endpoint, addresses[ID2], IDCODE_23_16_REG,
 | 
						|
	    &idcode_23_16);
 | 
						|
	if (r != OK) {
 | 
						|
		log_warn(&log, "Failed to read IDCODE part 3\n");
 | 
						|
	}
 | 
						|
 | 
						|
	r = i2creg_read8(bus_endpoint, addresses[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;
 | 
						|
	case 0:
 | 
						|
		log_debug(&log, "TPS65950 missing in qemu\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.m_lc_readclock_rtcdev.flags);
 | 
						|
			if (reply_status != OK) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			/* write results back to calling process */
 | 
						|
			reply_status =
 | 
						|
			    store_t(caller, m.m_lc_readclock_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,
 | 
						|
					    m.m_lc_readclock_rtcdev.grant, &t);
 | 
						|
				if (reply_status != OK) {
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				reply_status =
 | 
						|
				    rtc_set_time(&t,
 | 
						|
					    m.m_lc_readclock_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.m_readclock_lc_rtcdev.status = reply_status;
 | 
						|
 | 
						|
		log_debug(&log, "Sending Reply");
 | 
						|
 | 
						|
		r = ipc_sendnb(caller, &m);
 | 
						|
		if (r != OK) {
 | 
						|
			log_warn(&log, "ipc_sendnb() failed\n");
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	rtc_exit();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |