i2c_qmsi: Implement suspend and resume functions
In order to restore i2c context after entering SYS_PM_DEEP_SLEEP, suspend and resume functions are called. The following parameters are restored: - All registers for the I2C device. - I2C clocks. I2C depends on the DMA which is enabled separately. The suspend/resume functionality is implemented in the QMSI shim layer as a fast and temporary solution, it will be removed and migrated to QMSI later. Change-Id: I22ca2fb9884109f7b5ca75af572811187a531403 Signed-off-by: Julien Delayen <julien.delayen@intel.com>
This commit is contained in:
parent
0392d79845
commit
859a367d9c
|
@ -19,6 +19,7 @@
|
|||
#include <device.h>
|
||||
#include <i2c.h>
|
||||
#include <ioapic.h>
|
||||
#include <power.h>
|
||||
|
||||
#include "qm_i2c.h"
|
||||
#include "qm_isr.h"
|
||||
|
@ -33,15 +34,126 @@
|
|||
struct i2c_qmsi_config_info {
|
||||
qm_i2c_t instance; /* Controller instance. */
|
||||
union dev_config default_cfg;
|
||||
clk_periph_t clock_gate;
|
||||
};
|
||||
|
||||
struct i2c_context_t {
|
||||
uint32_t ic_con; /**< Control Register. */
|
||||
uint32_t ic_fs_spklen; /**< SS and FS Spike Suppression Limit. */
|
||||
uint32_t ic_hs_spklen; /**< HS Spike Suppression Limit. */
|
||||
uint32_t ic_ss_scl_lcnt; /**< Standard Speed Clock SCL Low Count. */
|
||||
uint32_t ic_ss_scl_hcnt; /**< Standard Speed Clock SCL High Count. */
|
||||
uint32_t ic_fs_scl_lcnt; /**< Fast Speed Clock SCL Low Count. */
|
||||
uint32_t ic_fs_scl_hcnt; /**< Fast Speed Clock SCL High Count. */
|
||||
uint32_t ic_hs_scl_lcnt; /**< High Speed Clock SCL Low Count. */
|
||||
uint32_t ic_hs_scl_hcnt; /**< High Speed Clock SCL High Count. */
|
||||
uint32_t ic_intr_mask; /**< Interrupt Mask. */
|
||||
uint32_t ic_sda_hold; /**< SDA Hold. */
|
||||
uint32_t ic_sda_setup; /**< SDA Setup. */
|
||||
uint32_t ic_ack_general_call; /**< General Call Ack. */
|
||||
uint32_t ic_sar; /**< Slave Address. */
|
||||
uint32_t ic_dma_cr; /**< DMA Control Register. */
|
||||
uint32_t ic_dma_tdlr; /**< DMA Transmit Data Level Register. */
|
||||
uint32_t ic_dma_rdlr; /**< DMA Receive Data Level Register. */
|
||||
uint32_t int_i2c_mst_mask; /**< Interrupt Mask. */
|
||||
};
|
||||
|
||||
static int i2c_qmsi_init(struct device *dev);
|
||||
|
||||
struct i2c_qmsi_driver_data {
|
||||
device_sync_call_t sync;
|
||||
int transfer_status;
|
||||
struct nano_sem sem;
|
||||
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
||||
struct i2c_context_t ctx_save;
|
||||
#endif
|
||||
};
|
||||
|
||||
static int i2c_qmsi_init(struct device *dev);
|
||||
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
||||
static int i2c_suspend_device(struct device *dev, int pm_policy)
|
||||
{
|
||||
if (device_busy_check(dev)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (pm_policy == SYS_PM_DEEP_SLEEP) {
|
||||
qm_i2c_t instance = GET_CONTROLLER_INSTANCE(dev);
|
||||
qm_i2c_reg_t *const regs = QM_I2C[instance];
|
||||
struct i2c_qmsi_driver_data *drv_data = dev->driver_data;
|
||||
struct i2c_context_t *const ctx_save = &drv_data->ctx_save;
|
||||
|
||||
ctx_save->ic_con = regs->ic_con;
|
||||
ctx_save->ic_fs_spklen = regs->ic_fs_spklen;
|
||||
ctx_save->ic_hs_spklen = regs->ic_hs_spklen;
|
||||
ctx_save->ic_ss_scl_lcnt = regs->ic_ss_scl_lcnt;
|
||||
ctx_save->ic_ss_scl_hcnt = regs->ic_ss_scl_hcnt;
|
||||
ctx_save->ic_fs_scl_lcnt = regs->ic_fs_scl_lcnt;
|
||||
ctx_save->ic_fs_scl_hcnt = regs->ic_fs_scl_hcnt;
|
||||
ctx_save->ic_hs_scl_lcnt = regs->ic_hs_scl_lcnt;
|
||||
ctx_save->ic_hs_scl_hcnt = regs->ic_hs_scl_hcnt;
|
||||
ctx_save->ic_intr_mask = regs->ic_intr_mask;
|
||||
ctx_save->ic_sda_hold = regs->ic_sda_hold;
|
||||
ctx_save->ic_sda_setup = regs->ic_sda_setup;
|
||||
ctx_save->ic_ack_general_call = regs->ic_ack_general_call;
|
||||
ctx_save->ic_sar = regs->ic_sar;
|
||||
ctx_save->ic_dma_cr = regs->ic_dma_cr;
|
||||
ctx_save->ic_dma_tdlr = regs->ic_dma_tdlr;
|
||||
ctx_save->ic_dma_rdlr = regs->ic_dma_rdlr;
|
||||
|
||||
if (instance == QM_I2C_0) {
|
||||
ctx_save->int_i2c_mst_mask =
|
||||
QM_SCSS_INT->int_i2c_mst_0_mask;
|
||||
} else {
|
||||
ctx_save->int_i2c_mst_mask =
|
||||
QM_SCSS_INT->int_i2c_mst_1_mask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i2c_resume_device(struct device *dev, int pm_policy)
|
||||
{
|
||||
if (pm_policy == SYS_PM_DEEP_SLEEP) {
|
||||
struct i2c_qmsi_config_info *config = dev->config->config_info;
|
||||
qm_i2c_reg_t *const regs = QM_I2C[config->instance];
|
||||
struct i2c_qmsi_driver_data *drv_data = dev->driver_data;
|
||||
struct i2c_context_t *const ctx_save = &drv_data->ctx_save;
|
||||
|
||||
regs->ic_con = ctx_save->ic_con;
|
||||
regs->ic_fs_spklen = ctx_save->ic_fs_spklen;
|
||||
regs->ic_hs_spklen = ctx_save->ic_hs_spklen;
|
||||
regs->ic_ss_scl_lcnt = ctx_save->ic_ss_scl_lcnt;
|
||||
regs->ic_ss_scl_hcnt = ctx_save->ic_ss_scl_hcnt;
|
||||
regs->ic_fs_scl_lcnt = ctx_save->ic_fs_scl_lcnt;
|
||||
regs->ic_fs_scl_hcnt = ctx_save->ic_fs_scl_hcnt;
|
||||
regs->ic_hs_scl_lcnt = ctx_save->ic_hs_scl_lcnt;
|
||||
regs->ic_hs_scl_hcnt = ctx_save->ic_hs_scl_hcnt;
|
||||
regs->ic_intr_mask = ctx_save->ic_intr_mask;
|
||||
regs->ic_sda_hold = ctx_save->ic_sda_hold;
|
||||
regs->ic_sda_setup = ctx_save->ic_sda_setup;
|
||||
regs->ic_ack_general_call = ctx_save->ic_ack_general_call;
|
||||
regs->ic_sar = ctx_save->ic_sar;
|
||||
regs->ic_dma_cr = ctx_save->ic_dma_cr;
|
||||
regs->ic_dma_tdlr = ctx_save->ic_dma_tdlr;
|
||||
regs->ic_dma_rdlr = ctx_save->ic_dma_rdlr;
|
||||
|
||||
if (config->instance == QM_I2C_0) {
|
||||
QM_SCSS_INT->int_i2c_mst_0_mask =
|
||||
ctx_save->int_i2c_mst_mask;
|
||||
} else {
|
||||
QM_SCSS_INT->int_i2c_mst_1_mask =
|
||||
ctx_save->int_i2c_mst_mask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_DEVICE_POWER_MANAGEMENT */
|
||||
|
||||
DEFINE_DEVICE_PM_OPS(i2c, i2c_suspend_device, i2c_resume_device);
|
||||
|
||||
#ifdef CONFIG_I2C_0
|
||||
|
||||
|
@ -50,10 +162,12 @@ static struct i2c_qmsi_driver_data driver_data_0;
|
|||
static struct i2c_qmsi_config_info config_info_0 = {
|
||||
.instance = QM_I2C_0,
|
||||
.default_cfg.raw = CONFIG_I2C_0_DEFAULT_CFG,
|
||||
.clock_gate = CLK_PERIPH_I2C_M0_REGISTER | CLK_PERIPH_CLK,
|
||||
};
|
||||
|
||||
DEVICE_INIT(i2c_0, CONFIG_I2C_0_NAME, i2c_qmsi_init, &driver_data_0,
|
||||
&config_info_0, SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
||||
DEVICE_INIT_PM(i2c_0, CONFIG_I2C_0_NAME, &i2c_qmsi_init,
|
||||
DEVICE_PM_OPS_GET(i2c), &driver_data_0, &config_info_0,
|
||||
SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
||||
|
||||
#endif /* CONFIG_I2C_0 */
|
||||
|
||||
|
@ -64,10 +178,12 @@ static struct i2c_qmsi_driver_data driver_data_1;
|
|||
static struct i2c_qmsi_config_info config_info_1 = {
|
||||
.instance = QM_I2C_1,
|
||||
.default_cfg.raw = CONFIG_I2C_1_DEFAULT_CFG,
|
||||
.clock_gate = CLK_PERIPH_I2C_M1_REGISTER | CLK_PERIPH_CLK,
|
||||
};
|
||||
|
||||
DEVICE_INIT(i2c_1, CONFIG_I2C_1_NAME, i2c_qmsi_init, &driver_data_1,
|
||||
&config_info_1, SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
||||
DEVICE_INIT_PM(i2c_1, CONFIG_I2C_1_NAME, &i2c_qmsi_init,
|
||||
DEVICE_PM_OPS_GET(i2c), &driver_data_1, &config_info_1,
|
||||
SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
||||
|
||||
#endif /* CONFIG_I2C_1 */
|
||||
|
||||
|
@ -125,8 +241,6 @@ static void transfer_complete(void *data, int rc, qm_i2c_status_t status,
|
|||
driver_data = GET_DRIVER_DATA(dev);
|
||||
driver_data->transfer_status = rc;
|
||||
device_sync_call_complete(&driver_data->sync);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static int i2c_qmsi_transfer(struct device *dev, struct i2c_msg *msgs,
|
||||
|
@ -140,6 +254,8 @@ static int i2c_qmsi_transfer(struct device *dev, struct i2c_msg *msgs,
|
|||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
device_busy_set(dev);
|
||||
|
||||
for (int i = 0; i < num_msgs; i++) {
|
||||
uint8_t op = msgs[i].flags & I2C_MSG_RW_MASK;
|
||||
bool stop = (msgs[i].flags & I2C_MSG_STOP) == I2C_MSG_STOP;
|
||||
|
@ -161,6 +277,7 @@ static int i2c_qmsi_transfer(struct device *dev, struct i2c_msg *msgs,
|
|||
nano_sem_give(&driver_data->sem);
|
||||
|
||||
if (rc != 0) {
|
||||
device_busy_clear(dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
@ -168,10 +285,13 @@ static int i2c_qmsi_transfer(struct device *dev, struct i2c_msg *msgs,
|
|||
device_sync_call_wait(&driver_data->sync);
|
||||
|
||||
if (driver_data->transfer_status != 0) {
|
||||
device_busy_clear(dev);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
device_busy_clear(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -201,8 +321,6 @@ static int i2c_qmsi_init(struct device *dev)
|
|||
(IOAPIC_LEVEL | IOAPIC_HIGH));
|
||||
irq_enable(QM_IRQ_I2C_0);
|
||||
QM_SCSS_INT->int_i2c_mst_0_mask &= ~BIT(0);
|
||||
|
||||
clk_periph_enable(CLK_PERIPH_I2C_M0_REGISTER | CLK_PERIPH_CLK);
|
||||
break;
|
||||
|
||||
#ifdef CONFIG_I2C_1
|
||||
|
@ -213,10 +331,11 @@ static int i2c_qmsi_init(struct device *dev)
|
|||
irq_enable(QM_IRQ_I2C_1);
|
||||
QM_SCSS_INT->int_i2c_mst_1_mask &= ~BIT(0);
|
||||
|
||||
clk_periph_enable(CLK_PERIPH_I2C_M1_REGISTER | CLK_PERIPH_CLK);
|
||||
break;
|
||||
#endif /* CONFIG_I2C_1 */
|
||||
|
||||
clk_periph_enable(config->clock_gate);
|
||||
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue