minix/drivers/tps65950/tps65950.c

361 lines
7.9 KiB
C
Raw Normal View History

#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;
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;
}