]> Zhao Yanbai Git Server - minix.git/commitdiff
tps65217: driver for the TPS65217 PMIC 97/697/2
authorThomas Cort <tcort@minix3.org>
Fri, 2 Aug 2013 14:10:48 +0000 (10:10 -0400)
committerThomas Cort <tcort@minix3.org>
Mon, 5 Aug 2013 14:22:59 +0000 (10:22 -0400)
Change-Id: Ic2259c15645816627d757c9c45560cb4c5c0156c

distrib/sets/lists/minix/md.evbarm
drivers/Makefile
drivers/tps65217/Makefile [new file with mode: 0644]
drivers/tps65217/README.txt [new file with mode: 0644]
drivers/tps65217/tps65217.c [new file with mode: 0644]
etc/system.conf
etc/usr/rc

index bf52cea7c0c2e711c245dd8973a0c2e381feac8c..031e291d3d94f07974496b1d44aeec9146dcfcee 100644 (file)
 ./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
index 47a85480cd39da4f6ff22d1f5ff6a5040eea6baf..97f33b9dd63f5ae29316086f83d4b2bc57d6d3d8 100644 (file)
@@ -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 (file)
index 0000000..a884550
--- /dev/null
@@ -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 <minix.service.mk>
diff --git a/drivers/tps65217/README.txt b/drivers/tps65217/README.txt
new file mode 100644 (file)
index 0000000..905894a
--- /dev/null
@@ -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 (file)
index 0000000..63d943f
--- /dev/null
@@ -0,0 +1,511 @@
+#include <minix/ds.h>
+#include <minix/drivers.h>
+#include <minix/i2c.h>
+#include <minix/i2cdriver.h>
+#include <minix/log.h>
+#include <minix/reboot.h>
+
+/* 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<<ACM_INT_DIS_BIT)|(1<<USBM_INT_DIS_BIT))
+
+/*
+ * Interrupt Status Bits - INT_REG[3:0]
+ */
+#define PBI_BIT 2
+#define ACI_BIT 1
+#define USBI_BIT 0
+#define PBI_MASK (1<<PBI_BIT)
+
+/*
+ * Power Off Bit - STATUS[7]
+ */
+#define OFF_BIT 7
+#define PWR_OFF_MASK (1<<OFF_BIT)
+
+/* The TPS65217 is connected to the NMI pin of the AM335X on the BeagleBone and
+ * BeagleBone Black. That line is used to signal to the SoC that an interrupt
+ * has happened in the TPS65217. The NMI pin in turn generates an interrupt
+ * in the SoC which this driver will receive.
+ */
+static int irq = 7;
+static int irq_hook_id = 7;
+static int irq_hook_kernel_id = 7;
+
+/* Only valid slave address for this device is 0x24 */
+static i2c_addr_t valid_addrs[2] = {
+       0x24, 0x00
+};
+
+/* the bus that this device is on (counting starting at 1) */
+static uint32_t bus;
+
+/* slave address of the device */
+static i2c_addr_t address;
+
+/* endpoint for the driver for the bus itself. */
+static endpoint_t bus_endpoint;
+
+/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
+static struct log log = {
+       .name = "tps65217",
+       .log_level = LEVEL_INFO,
+       .log_func = default_log
+};
+
+/* Register Access */
+static int reg_read(uint8_t reg, uint8_t * val);
+static int reg_write(uint8_t reg, uint8_t val);
+
+/* Device Specific Functions */
+static int check_revision(void);
+static int enable_pwr_off(void);
+static int intr_enable(void);
+static int intr_handler(void);
+static void do_shutdown(int how);
+
+/* SEF Related Function Prototypes */
+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);
+
+static int
+reg_read(uint8_t reg, uint8_t * val)
+{
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       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 = address;
+
+       /* 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;
+}
+
+static int
+reg_write(uint8_t reg, uint8_t val)
+{
+       int r;
+       minix_i2c_ioctl_exec_t ioctl_exec;
+
+       if (reg >= 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;
+}
index 92356994c39588325c2f8006090feb2c1fe80144..4d0637d1b8e68a37eb6efa97c828c8ae6632130d 100644 (file)
@@ -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
index 999d4aac2978728070aad6514dbec6205accd9a7..e3a1fa07fd4fa6c289b3c3a873041e4abd7fc360 100644 (file)
@@ -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'
                        ;;