soc: ti: wkup_m3_ipc: Add support for i2c voltage scaling
authorDave Gerlach <d-gerlach@ti.com>
Fri, 2 Nov 2018 10:29:08 +0000 (15:59 +0530)
committerTero Kristo <t-kristo@ti.com>
Tue, 6 Nov 2018 13:25:14 +0000 (15:25 +0200)
Allow loading of a binary containing i2c scaling sequences to be
provided to the wkup_m3 firmware in order to properly scale voltage
rails on the PMIC during low power modes like DeepSleep0. Proper binary
format is determined by the FW in use.

Code expects firmware to have 0x0C57 present as the first two bytes
followed by one byte defining offset to sleep sequence followed by one
byte defining offset to wake sequence and then lastly both sequences.
Each sequence is a series of I2C transfers in the form:

u8 length | u8 chip address | u8 byte0/reg address | u8 byte1 | u8 byteN
..

The length indicates the number of bytes to transfer, including the
register address. The length of each transfer is limited by the I2C
buffer size of 32 bytes.

Based on previous work by Russ Dill.

Signed-off-by: Dave Gerlach <d-gerlach@ti.com>
Signed-off-by: Keerthy <j-keerthy@ti.com>
Documentation/devicetree/bindings/soc/ti/wkup_m3_ipc.txt
drivers/soc/ti/wkup_m3_ipc.c
include/linux/wkup_m3_ipc.h

index 2d6c66ba1cdaf76dc9cfa130403501524de30e80..fabfc955099ea151df63b7e2f9dcf0a7f4c81bda 100644 (file)
@@ -56,6 +56,17 @@ Example:
                };
        };
 
+Support for I2C PMIC Voltage Scaling
+--------------------
+It is possible to pass the name of a binary file to laod to the CM3 firmware
+in order to provide I2C sequences for the CM3 to send out to the PMIC during
+low power mode entry.
+
+Optional properties:
+--------------------
+- scale-data-fw:       Name of the firmware binary in /lib/firmware to copy to m3
+                       aux data.
+
 Support for VTT Toggle
 ==================================
 In order to enable the support for VTT toggle during Suspend/Resume
index 75b99b7e23ce0c08a8dfc3b488a56d55d1ebfeeb..a32412906b3ae358cde7c5d02f895e364868f12b 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include <linux/err.h>
+#include <linux/firmware.h>
 #include <linux/kernel.h>
 #include <linux/kthread.h>
 #include <linux/interrupt.h>
 #define M3_STATE_MSG_FOR_LP            3
 #define M3_STATE_MSG_FOR_RESET         4
 
+#define WKUP_M3_SD_FW_MAGIC            0x570C
+
+#define WKUP_M3_DMEM_START             0x80000
+#define WKUP_M3_AUXDATA_OFFSET         0x1000
+#define WKUP_M3_AUXDATA_SIZE           0xFF
+
 static struct wkup_m3_ipc *m3_ipc_state;
 
 static const struct wkup_m3_wakeup_src wakeups[] = {
@@ -82,6 +89,80 @@ static const struct wkup_m3_wakeup_src wakeups[] = {
        {.irq_nr = 0,   .src = "Unknown"},
 };
 
+/**
+ * wkup_m3_copy_aux_data - Copy auxiliary data to special region of m3 dmem
+ * @data - pointer to data
+ * @sz - size of data to copy (limit 256 bytes)
+ *
+ * Copies any additional blob of data to the wkup_m3 dmem to be used by the
+ * firmware
+ */
+static unsigned long wkup_m3_copy_aux_data(struct wkup_m3_ipc *m3_ipc,
+                                          const void *data, int sz)
+{
+       unsigned long aux_data_dev_addr;
+       void *aux_data_addr;
+
+       aux_data_dev_addr = WKUP_M3_DMEM_START + WKUP_M3_AUXDATA_OFFSET;
+       aux_data_addr = rproc_da_to_va(m3_ipc->rproc,
+                                      aux_data_dev_addr,
+                                      WKUP_M3_AUXDATA_SIZE);
+       memcpy(aux_data_addr, data, sz);
+
+       return WKUP_M3_AUXDATA_OFFSET;
+}
+
+static void wkup_m3_scale_data_fw_cb(const struct firmware *fw, void *context)
+{
+       unsigned long val, aux_base;
+       struct wkup_m3_scale_data_header hdr;
+       struct wkup_m3_ipc *m3_ipc = context;
+       struct device *dev = m3_ipc->dev;
+
+       if (!fw) {
+               dev_err(dev, "Voltage scale fw name given but file missing.\n");
+               return;
+       }
+
+       memcpy(&hdr, fw->data, sizeof(hdr));
+
+       if (hdr.magic != WKUP_M3_SD_FW_MAGIC) {
+               dev_err(dev, "PM: Voltage Scale Data binary does not appear valid.\n");
+               goto release_sd_fw;
+       }
+
+       aux_base = wkup_m3_copy_aux_data(m3_ipc, fw->data + sizeof(hdr),
+                                        fw->size - sizeof(hdr));
+
+       val = (aux_base + hdr.sleep_offset);
+       val |= ((aux_base + hdr.wake_offset) << 16);
+
+       m3_ipc->volt_scale_offsets = val;
+
+release_sd_fw:
+       release_firmware(fw);
+};
+
+static int wkup_m3_init_scale_data(struct wkup_m3_ipc *m3_ipc,
+                                  struct device *dev)
+{
+       int ret = 0;
+
+       /*
+        * If no name is provided, user has already been warned, pm will
+        * still work so return 0
+        */
+
+       if (!m3_ipc->sd_fw_name)
+               return ret;
+
+       ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+                                     m3_ipc->sd_fw_name, dev, GFP_ATOMIC,
+                                     m3_ipc, wkup_m3_scale_data_fw_cb);
+
+       return ret;
+}
+
 static void am33xx_txev_eoi(struct wkup_m3_ipc *m3_ipc)
 {
        writel(AM33XX_M3_TXEV_ACK,
@@ -146,6 +227,7 @@ static irqreturn_t wkup_m3_txev_handler(int irq, void *ipc_data)
                }
 
                m3_ipc->state = M3_STATE_INITED;
+               wkup_m3_init_scale_data(m3_ipc, dev);
                complete(&m3_ipc->sync_complete);
                break;
        case M3_STATE_MSG_FOR_RESET:
@@ -303,12 +385,15 @@ static int wkup_m3_prepare_low_power(struct wkup_m3_ipc *m3_ipc, int state)
        switch (state) {
        case WKUP_M3_DEEPSLEEP:
                m3_power_state = IPC_CMD_DS0;
+               wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->volt_scale_offsets, 5);
                break;
        case WKUP_M3_STANDBY:
                m3_power_state = IPC_CMD_STANDBY;
+               wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
                break;
        case WKUP_M3_IDLE:
                m3_power_state = IPC_CMD_IDLE;
+               wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
                break;
        default:
                return 1;
@@ -322,7 +407,6 @@ static int wkup_m3_prepare_low_power(struct wkup_m3_ipc *m3_ipc, int state)
                               m3_ipc->isolation_conf, 4);
        wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
        wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 3);
-       wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
        wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 6);
        wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 7);
 
@@ -531,6 +615,12 @@ static int wkup_m3_ipc_probe(struct platform_device *pdev)
        if (of_find_property(np, "ti,set-io-isolation", NULL))
                wkup_m3_set_io_isolation(m3_ipc);
 
+       ret = of_property_read_string(np, "ti,scale-data-fw",
+                                     &m3_ipc->sd_fw_name);
+       if (ret) {
+               dev_dbg(dev, "Voltage scaling data blob not provided from DT.\n");
+       };
+
        /*
         * Wait for firmware loading completion in a thread so we
         * can boot the wkup_m3 as soon as it's ready without holding
index bd79c1fcf299f52eac6daee9e87098804eae331a..071a7be37ed8229735f70dadfe1e61aa41959fda 100644 (file)
@@ -37,6 +37,9 @@ struct wkup_m3_ipc {
        int isolation_conf;
        int state;
 
+       unsigned long volt_scale_offsets;
+       const char *sd_fw_name;
+
        struct completion sync_complete;
        struct mbox_client mbox_client;
        struct mbox_chan *mbox;
@@ -50,6 +53,12 @@ struct wkup_m3_wakeup_src {
        char src[10];
 };
 
+struct wkup_m3_scale_data_header {
+       u16 magic;
+       u8 sleep_offset;
+       u8 wake_offset;
+} __packed;
+
 struct wkup_m3_ipc_ops {
        void (*set_mem_type)(struct wkup_m3_ipc *m3_ipc, int mem_type);
        void (*set_resume_address)(struct wkup_m3_ipc *m3_ipc, void *addr);