tps65950: driver for the TPS65950 PMIC

Change-Id: I6b6163e59233d1f823f03550b949d53e1738a7f4
This commit is contained in:
Thomas Cort 2013-08-06 09:52:46 -04:00 committed by Gerrit Code Review
parent 3a0aa55040
commit bab2a34e1b
11 changed files with 804 additions and 1 deletions

View file

@ -113,5 +113,6 @@
./usr/sbin/random minix-sys
./usr/sbin/tda19988 minix-sys
./usr/sbin/tps65217 minix-sys
./usr/sbin/tps65950 minix-sys
./usr/tests/minix-posix/mod minix-sys
./usr/tests/minix-posix/test63 minix-sys

View file

@ -24,7 +24,7 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \
.if ${MACHINE_ARCH} == "earm"
SUBDIR= cat24c256 fb gpio i2c mmc lan8710a log readclock \
tda19988 tps65217 tty random
tda19988 tps65217 tps65950 tty random
.endif
.endif # ${MKIMAGEONLY} != "yes"

14
drivers/tps65950/Makefile Normal file
View file

@ -0,0 +1,14 @@
# Makefile for the tps65950 PMIC found on the BeagleBoard-xM.
PROG= tps65950
SRCS= tps65950.c tps65950.h rtc.c rtc.h
DPADD+= ${LIBI2CDRIVER} ${LIBSYS} ${LIBTIMERS}
LDADD+= -li2cdriver -lsys -ltimers
MAN=
BINDIR?= /usr/sbin
CPPFLAGS+= -I${NETBSDSRCDIR}
.include <minix.service.mk>

View file

@ -0,0 +1,36 @@
TPS65950 Driver (Power Management IC)
=====================================
Overview
--------
This driver is for the power management chip commonly found on the
BeagleBoard-xM.
Limitations
-----------
The TPS65950 has a pin labelled MSECURE which provides a form of write
protection. Depending on the pin's state (high or low), writing to certain
registers is disabled or enabled. The pin is driven by a GPIO on the SoC.
There isn't a good way to access that pin on the SoC yet, so the PMIC
is always in the default insecure mode. It isn't really insecure as this
driver is the only one that the i2c bus driver will allow to access the
PMIC.
This is a huge chip. It has two I2C controllers, one with 4 slave addresses
hosting 256 registers per address. The TRM is over 900 pages. Given the
limited usefulness of some peripherals on the chip, not everything is
implemented.
Testing the Code
----------------
Starting up an instance:
/bin/service up /usr/sbin/tps65950 -label tps65950.1.48 \
-args 'bus=1 address=0x48'
Killing an instance:
/bin/service down tps65950.1.48

181
drivers/tps65950/rtc.c Normal file
View file

@ -0,0 +1,181 @@
#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);
}

11
drivers/tps65950/rtc.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef __RTC_H
#define __RTC_H
#include <time.h>
int rtc_init(void);
int rtc_get_time(struct tm *t, int flags); /* read the hardware clock into t */
int rtc_set_time(struct tm *t, int flags); /* set the hardware clock to t */
int rtc_exit(void);
#endif /* __RTC_H */

473
drivers/tps65950/tps65950.c Normal file
View file

@ -0,0 +1,473 @@
#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;
}

View file

@ -0,0 +1,72 @@
#ifndef __TPS65950_H
#define __TPS65950_H
/*
* The chip is so huge it's split into multiple sub-modules, each with 256
* byte register maps on it's own slave addresses. Use ID1, ID2, ... to
* specify which sub-module to communicate with. Each slave address can have
* registers for one or more peripherals.
*/
/* Slave Address 0x48 (USB) */
#define ID1 0
/* Slave Address 0x49 (AUDIO_VOICE/GPIO/INTBR/PIH/TEST) */
#define ID2 1
/* Interface Bit Registers (INTBR) */
/* Chip Identifier
*
* When combined to uint32_t, these are the subfields:
* [31:28] - Silicon version number
* [27:12] - Part code number
* [11: 1] - Manufacturer code
* [ 0] - Least-significant bit (LSB)
*/
#define IDCODE_7_0_REG 0x85
#define IDCODE_15_8_REG 0x86
#define IDCODE_23_16_REG 0x87
#define IDCODE_31_24_REG 0x88
#define IDCODE_REV_1_0 0x00009002f
#define IDCODE_REV_1_1 0x01009002f
#define IDCODE_REV_1_2 0x03009002f
/* Write 0x49 to UNLOCK_TEST_REG to unlock reading of IDCODE and DIEID */
#define UNLOCK_TEST_REG 0x97
#define UNLOCK_TEST_CODE 0x49
/* Slave Address 0x4a (BCI/MADC/MAIN_CHARGE/PWM01/LED/PWMAB/KEYPAD) */
#define ID3 2
/* Slave Address 0x4b (BACKUP_REG/INT/PM_MASTER/SECURED_REG) */
#define ID4 3
/*
* Real Time Clock
*/
#define SECONDS_REG 0x1c
#define MINUTES_REG 0x1d
#define HOURS_REG 0x1e
#define DAYS_REG 0x1f
#define MONTHS_REG 0x20
#define YEARS_REG 0x21
#define RTC_CTRL_REG 0x00000029
#define GET_TIME_BIT 6
#define STOP_RTC_BIT 0
#define RTC_STATUS_REG 0x0000002A
#define RUN_BIT 1
int reg_read(uint8_t id, uint8_t reg, uint8_t * val);
int reg_write(uint8_t id, uint8_t reg, uint8_t val);
int reg_set(uint8_t id, uint8_t reg, uint8_t mask);
int reg_clear(uint8_t id, uint8_t reg, uint8_t mask);
#endif /* __TPS65950_H */

View file

@ -631,6 +631,11 @@ service tps65217
ipc SYSTEM RS DS PM i2c;
};
service tps65950
{
ipc SYSTEM RS DS i2c readclock.drv;
};
service vbox
{
system

View file

@ -228,6 +228,11 @@ start)
UNKNOWN)
echo "Unable to detect board -- assuming BeagleBoard-xM"
echo -n "Starting i2c device drivers: "
# Start TPS65950 driver for power management.
up tps65950 -label tps65950.1.48 \
-args 'bus=1 address=0x48'
;;
esac

View file

@ -1322,6 +1322,10 @@
#define RTCDEV_SET_TIME (RTCDEV_RQ_BASE + 1) /* set time in hw clock */
#define RTCDEV_PWR_OFF (RTCDEV_RQ_BASE + 2) /* set time to cut the power */
/* Same as GET/SET above but using grants */
#define RTCDEV_GET_TIME_G (RTCDEV_RQ_BASE + 3) /* get time from hw clock */
#define RTCDEV_SET_TIME_G (RTCDEV_RQ_BASE + 4) /* set time in hw clock */
/* Message types for real time clock responses. */
#define RTCDEV_REPLY (RTCDEV_RS_BASE + 0) /* general reply code */
@ -1329,6 +1333,7 @@
#define RTCDEV_TM m2_p1 /* pointer to struct tm */
#define RTCDEV_FLAGS m2_s1 /* clock flags flags */
#define RTCDEV_STATUS m2_i2 /* OK or error code */
#define RTCDEV_GRANT m2_p1 /* grant containing struct tm */
/* Bits in 'RTCDEV_FLAGS' field of real time clock requests. */
#define RTCDEV_NOFLAGS 0x00 /* no flags are set */