From d9b62047f11992d8ff3e5da25f6837e68032023b Mon Sep 17 00:00:00 2001 From: Thomas Cort Date: Fri, 2 Aug 2013 10:10:48 -0400 Subject: [PATCH] tps65217: driver for the TPS65217 PMIC Change-Id: Ic2259c15645816627d757c9c45560cb4c5c0156c --- distrib/sets/lists/minix/md.evbarm | 1 + drivers/Makefile | 3 +- drivers/tps65217/Makefile | 14 + drivers/tps65217/README.txt | 20 ++ drivers/tps65217/tps65217.c | 511 +++++++++++++++++++++++++++++ etc/system.conf | 8 + etc/usr/rc | 14 +- 7 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 drivers/tps65217/Makefile create mode 100644 drivers/tps65217/README.txt create mode 100644 drivers/tps65217/tps65217.c diff --git a/distrib/sets/lists/minix/md.evbarm b/distrib/sets/lists/minix/md.evbarm index bf52cea7c..031e291d3 100644 --- a/distrib/sets/lists/minix/md.evbarm +++ b/distrib/sets/lists/minix/md.evbarm @@ -112,5 +112,6 @@ ./usr/sbin/lan8710a minix-sys ./usr/sbin/random minix-sys ./usr/sbin/tda19988 minix-sys +./usr/sbin/tps65217 minix-sys ./usr/tests/minix-posix/mod minix-sys ./usr/tests/minix-posix/test63 minix-sys diff --git a/drivers/Makefile b/drivers/Makefile index 47a85480c..97f33b9dd 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -23,7 +23,8 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \ .endif .if ${MACHINE_ARCH} == "earm" -SUBDIR= cat24c256 fb gpio i2c mmc log readclock tda19988 tty random lan8710a +SUBDIR= cat24c256 fb gpio i2c mmc lan8710a log readclock \ + tda19988 tps65217 tty random .endif .endif # ${MKIMAGEONLY} != "yes" diff --git a/drivers/tps65217/Makefile b/drivers/tps65217/Makefile new file mode 100644 index 000000000..a884550be --- /dev/null +++ b/drivers/tps65217/Makefile @@ -0,0 +1,14 @@ +# Makefile for the tps65217 Power Management IC found on the BeagleBones +PROG= tps65217 +SRCS= tps65217.c + +DPADD+= ${LIBI2CDRIVER} ${CLKCONF} ${LIBSYS} ${LIBTIMERS} +LDADD+= -li2cdriver -lclkconf -lsys -ltimers + +MAN= + +BINDIR?= /usr/sbin + +CPPFLAGS+= -I${NETBSDSRCDIR} + +.include diff --git a/drivers/tps65217/README.txt b/drivers/tps65217/README.txt new file mode 100644 index 000000000..905894a39 --- /dev/null +++ b/drivers/tps65217/README.txt @@ -0,0 +1,20 @@ +TPS65217 Driver (Power Management IC) +===================================== + +Overview +-------- + +This driver is for the power management chip commonly found on the BeagleBone +and the BeagleBone Black. + +Testing the Code +---------------- + +Starting up an instance: + +/bin/service up /usr/sbin/tps65217 -label tps65217.1.24 \ + -args 'bus=1 address=0x24' + +Killing an instance: + +/bin/service down tps65217.1.24 diff --git a/drivers/tps65217/tps65217.c b/drivers/tps65217/tps65217.c new file mode 100644 index 000000000..63d943f6a --- /dev/null +++ b/drivers/tps65217/tps65217.c @@ -0,0 +1,511 @@ +#include +#include +#include +#include +#include +#include + +/* Register Addresses */ +#define CHIPID_REG 0x00 +#define PPATH_REG 0x01 +#define INT_REG 0x02 +#define CHGCONFIG0_REG 0x03 +#define CHGCONFIG1_REG 0x04 +#define CHGCONFIG2_REG 0x05 +#define CHGCONFIG3_REG 0x06 +#define WLEDCTRL1_REG 0x07 +#define WLEDCTRL2_REG 0x08 +#define MUXCTRL_REG 0x09 +#define STATUS_REG 0x0a +#define PASSWORD_REG 0x0b +#define PGOOD_REG 0x0c +#define DEFPG_REG 0x0d +#define DEFDCDC1_REG 0x0e +#define DEFDCDC2_REG 0x0f +#define DEFDCDC3_REG 0x10 +#define DEFSLEW_REG 0x11 +#define DEFLDO1_REG 0x12 +#define DEFLDO2_REG 0x13 +#define DEFLS1_REG 0x14 +#define DEFLS2_REG 0x15 +#define ENABLE_REG 0x16 +/* no documented register at 0x17 */ +#define DEFUVLO_REG 0x18 +#define SEQ1_REG 0x19 +#define SEQ2_REG 0x1a +#define SEQ3_REG 0x1b +#define SEQ4_REG 0x1c +#define SEQ5_REG 0x1d +#define SEQ6_REG 0x1e + +/* Bits and Masks */ + +/* + * CHIP masks - CHIPID_REG[7:4] + */ +#define TPS65217A_CHIP_MASK 0x70 +#define TPS65217B_CHIP_MASK 0xf0 +#define TPS65217C_CHIP_MASK 0xe0 +#define TPS65217D_CHIP_MASK 0x60 + +/* + * Interrupt Enable/Disable Bits/Masks - INT_REG[6:4] + * 0=Enable 1=Disable | Default mask: Disable ACM, USBM ~ Enable only PBM + */ +#define PBM_INT_DIS_BIT 6 +#define ACM_INT_DIS_BIT 5 +#define USBM_INT_DIS_BIT 4 +#define DEFAULT_INT_MASK ((1<= 0x0d) { + /* TODO: writes to password protected registers hasn't + * been implemented since nothing in this driver needs to + * write to them. When needed, it should be implemented. + */ + log_warn(&log, "Cannot write to protected registers."); + return -1; + } + + 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 = address; + + /* 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; +} + +static int +check_revision(void) +{ + int r; + uint8_t chipid; + + r = reg_read(CHIPID_REG, &chipid); + if (r != OK) { + log_warn(&log, "Failed to read CHIPID\n"); + return -1; + } + + switch (chipid & 0xf0) { + case TPS65217A_CHIP_MASK: + log_debug(&log, "TPS65217A rev 1.%d\n", (chipid & 0x0f)); + break; + case TPS65217B_CHIP_MASK: + log_debug(&log, "TPS65217B rev 1.%d\n", (chipid & 0x0f)); + break; + case TPS65217C_CHIP_MASK: + log_debug(&log, "TPS65217C rev 1.%d\n", (chipid & 0x0f)); + break; + case TPS65217D_CHIP_MASK: + log_debug(&log, "TPS65217D rev 1.%d\n", (chipid & 0x0f)); + break; + default: + log_warn(&log, "Unexpected CHIPID: 0x%x\n", chipid); + return -1; + } + + return OK; +} + +static int +enable_pwr_off(void) +{ + int r; + + /* enable power off via the PWR_EN pin. just do the setup here. + * the kernel will do the work to toggle the pin when the + * system is ready to be powered off. Should be called during startup + * so that shutdown(8) can do power-off with reboot(RBT_POWEROFF). + */ + r = reg_write(STATUS_REG, PWR_OFF_MASK); + if (r != OK) { + log_warn(&log, "Cannot set power off mask."); + return -1; + } + + return r; +} + +static int +intr_enable(void) +{ + int r; + uint8_t val; + static int policy_set = 0; + static int irq_enabled = 0; + + /* Enable IRQ */ + if (!policy_set) { + r = sys_irqsetpolicy(irq, 0, &irq_hook_kernel_id); + if (r == OK) { + policy_set = 1; + } else { + log_warn(&log, "Couldn't set irq policy\n"); + return -1; + } + } + if (policy_set && !irq_enabled) { + r = sys_irqenable(&irq_hook_kernel_id); + if (r == OK) { + irq_enabled = 1; + } else { + log_warn(&log, "Couldn't enable irq %d (hooked)\n", + irq); + return -1; + } + } + + /* Enable/Disable interrupts in the TPS65217 */ + r = reg_write(INT_REG, DEFAULT_INT_MASK); + if (r != OK) { + log_warn(&log, "Failed to set interrupt mask.\n"); + return -1; + } + + /* Read from the interrupt register to clear any pending interrupts */ + r = reg_read(INT_REG, &val); + if (r != OK) { + log_warn(&log, "Failed to read interrupt register.\n"); + return -1; + } + + return OK; +} + +static int +intr_handler(void) +{ + int r; + uint8_t val; + struct tm t; + + /* read interrupt register to get interrupt that fired and clear it */ + r = reg_read(INT_REG, &val); + if (r != OK) { + log_warn(&log, "Failed to read interrupt register.\n"); + return -1; + } + + if ((val & PBI_MASK) != 0) { + log_info(&log, "Power Button Pressed\n"); + reboot(RBT_POWEROFF); + log_warn(&log, "Failed to power off the system."); + sys_irqenable(&irq_hook_kernel_id); + return -1; + } + + /* re-enable interrupt */ + r = sys_irqenable(&irq_hook_kernel_id); + if (r != OK) { + log_warn(&log, "Unable to renable IRQ (r=%d)\n", r); + return -1; + } + + return OK; +} + +static int +sef_cb_lu_state_save(int UNUSED(state)) +{ + ds_publish_u32("bus", bus, DSF_OVERWRITE); + ds_publish_u32("address", address, 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; + + ds_retrieve_u32("address", &value); + ds_delete_u32("address"); + address = (int) value; + + return OK; +} + +static int +sef_cb_init(int type, sef_init_info_t * UNUSED(info)) +{ + int r; + + 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; + } + + /* claim the device */ + r = i2cdriver_reserve_device(bus_endpoint, address); + if (r != OK) { + log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n", + address, 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 CHIPID\n"); + return EXIT_FAILURE; + } + + /* enable interrupts */ + r = intr_enable(); + if (r != OK) { + log_warn(&log, "Failed to enable interrupts.\n"); + return EXIT_FAILURE; + } + + /* enable power-off pin so the kernel can cut power to the SoC */ + enable_pwr_off(); + + 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; + endpoint_t user, caller; + message m; + int ipc_status; + + env_setargs(argc, argv); + + r = i2cdriver_env_parse(&bus, &address, valid_addrs); + if (r < 0) { + log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n"); + log_warn(&log, "Example -args 'bus=1 address=0x24'\n"); + return EXIT_FAILURE; + } else if (r > 0) { + log_warn(&log, + "Invalid slave address for device, expecting 0x24\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; + } + + log_trace(&log, "Got a message 0x%x from 0x%x\n", m.m_type, + m.m_source); + + if (is_ipc_notify(ipc_status)) { + + switch (m.m_source) { + + case DS_PROC_NR: + /* bus driver changed state, update endpoint */ + i2cdriver_handle_bus_update(&bus_endpoint, bus, + address); + break; + case HARDWARE: + intr_handler(); + break; + default: + break; + } + + /* Do not reply to notifications. */ + continue; + } + + caller = m.m_source; + user = m.USER_ENDPT; + + /* + * Handle Message + * + * So far this driver only deals with notifications + * so it always replies to non-notifications with EINVAL. + */ + + /* Send Reply */ + m.m_type = TASK_REPLY; + m.REP_ENDPT = user; + m.REP_STATUS = EINVAL; + + r = sendnb(caller, &m); + if (r != OK) { + log_warn(&log, "sendnb() failed\n"); + continue; + } + } + + return 0; +} diff --git a/etc/system.conf b/etc/system.conf index 92356994c..4d0637d1b 100644 --- a/etc/system.conf +++ b/etc/system.conf @@ -621,6 +621,14 @@ service tda19988 ipc SYSTEM RS DS i2c; }; +service tps65217 +{ + uid 0; # needed for doing reboot(RBT_POWEROFF) + system IRQCTL PRIVCTL; + irq 7; # NNMI pin on BeagleBone / BeagleBone Black + ipc SYSTEM RS DS PM i2c; +}; + service vbox { system diff --git a/etc/usr/rc b/etc/usr/rc index 999d4aac2..e3a1fa07f 100644 --- a/etc/usr/rc +++ b/etc/usr/rc @@ -202,7 +202,13 @@ start) echo -n "Starting i2c device drivers: " test -e /dev/eepromb1s50 || (cd /dev && MAKEDEV eepromb1s50) up cat24c256 -dev /dev/eepromb1s50 \ - -label cat24c256.1.50 -args 'bus=1 address=0x50' + -label cat24c256.1.50 \ + -args 'bus=1 address=0x50' + + # Start TPS65217 driver for power management. + up tps65217 -label tps65217.1.24 \ + -args 'bus=1 address=0x24' + ;; A335BNLT) echo "Detected BeagleBone Black" @@ -211,7 +217,11 @@ start) up cat24c256 -dev /dev/eepromb1s50 \ -label cat24c256.1.50 -args 'bus=1 address=0x50' - # Start TDA19988 driver for EDID reading. + # Start TPS65217 driver for power management. + up tps65217 -label tps65217.1.24 \ + -args 'bus=1 address=0x24' + + # Start TDA19988 driver for reading EDID. up tda19988 -label tda19988.1.3470 -args \ 'cec_bus=1 cec_address=0x34 hdmi_bus=1 hdmi_address=0x70' ;;