ITE: drivers/i2c: Add command queue mode

Adding command queue mode can reduce the time between each byte to
improve the I2C bus clock stretching during I2C transaction.

I2C command queue mode of it8xxx2 can support I2C APIs including:
i2c_write(), i2c_read(), i2c_burst_read.

Test:
1. tests\drivers\i2c\i2c_api --> pass
2. Reading 16 bytes of data through i2c_burst_read() can reduce
   0.72ms(2.54ms->1.82ms) compared to the original pio mode when the
   frequency is 100KHz.
3. krabby platform can boot normally.

Signed-off-by: Tim Lin <tim2.lin@ite.corp-partner.google.com>
This commit is contained in:
Tim Lin 2022-05-18 18:40:19 +08:00 committed by Carles Cufí
parent 4436fb194e
commit 04c6d7569f
6 changed files with 438 additions and 30 deletions

View file

@ -20,3 +20,26 @@ config I2C_ITE_ENHANCE
help
This option can enable the enhance I2C
of IT8XXX2 and support three channels.
if I2C_ITE_ENHANCE
config I2C_IT8XXX2_CQ_MODE
bool "IT8XXX2 I2C command queue mode"
default y
select SOC_IT8XXX2_CPU_IDLE_GATING
help
This is an option to enable command queue mode which can
reduce the time between each byte to improve the I2C bus
clock stretching during I2C transaction.
I2C command queue mode of it8xxx2 can support I2C APIs
including: i2c_write(), i2c_read(), i2c_burst_read.
config I2C_CQ_MODE_MAX_PAYLOAD_SIZE
int "It is allowed to configure the size up to 2K bytes."
range 32 2048
default 64
help
This is the command queue mode payload size which size
up to 2k bytes.
endif # I2C_ITE_ENHANCE

View file

@ -26,6 +26,34 @@ LOG_MODULE_REGISTER(i2c_ite_enhance, CONFIG_I2C_LOG_LEVEL);
#define I2C_LINE_SDA_HIGH BIT(1)
#define I2C_LINE_IDLE (I2C_LINE_SCL_HIGH | I2C_LINE_SDA_HIGH)
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
/* Reserved 5 bytes for ID and CMD_x. */
#define I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE (CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE - 5)
/* Repeat Start. */
#define I2C_CQ_CMD_L_RS BIT(7)
/*
* R/W (Read/ Write) decides the I2C read or write direction.
* 1: read, 0: write
*/
#define I2C_CQ_CMD_L_RW BIT(6)
/* P (STOP) is the I2C STOP condition. */
#define I2C_CQ_CMD_L_P BIT(5)
/* E (End) is this device end flag. */
#define I2C_CQ_CMD_L_E BIT(4)
/* LA (Last ACK) is Last ACK in master receiver. */
#define I2C_CQ_CMD_L_LA BIT(3)
/* bit[2:0] are number of transfer out or receive data which depends on R/W. */
#define I2C_CQ_CMD_L_NUM_BIT_2_0 GENMASK(2, 0)
struct i2c_cq_packet {
uint8_t id;
uint8_t cmd_l;
uint8_t cmd_h;
uint8_t wdata[0];
};
#endif /* CONFIG_I2C_IT8XXX2_CQ_MODE */
struct i2c_enhance_config {
void (*irq_config_func)(void);
uint32_t bitrate;
@ -71,6 +99,16 @@ struct i2c_enhance_data {
uint16_t addr_16bit;
/* wait for stop bit interrupt */
uint8_t stop;
/* Number of messages. */
uint8_t num_msgs;
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
/* Store command queue mode messages. */
struct i2c_msg *cq_msgs;
/* Command queue tx payload. */
uint8_t i2c_cq_mode_tx_dlm[CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE] __aligned(4);
/* Command queue rx payload. */
uint8_t i2c_cq_mode_rx_dlm[CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE] __aligned(4);
#endif
};
enum enhanced_i2c_transfer_direct {
@ -101,6 +139,8 @@ enum enhanced_i2c_ctl {
E_START_ID = (E_INT_EN | E_MODE_SEL | E_ACK | E_START | E_HW_RST),
/* Generate stop condition */
E_FINISH = (E_INT_EN | E_MODE_SEL | E_ACK | E_STOP | E_HW_RST),
/* Start with command queue mode */
E_START_CQ = (E_INT_EN | E_MODE_SEL | E_ACK | E_START),
};
enum enhanced_i2c_host_status {
@ -482,46 +522,23 @@ static int i2c_transaction(const struct device *dev)
return 0;
}
static int i2c_enhance_transfer(const struct device *dev, struct i2c_msg *msgs,
uint8_t num_msgs, uint16_t addr)
static int i2c_enhance_pio_transfer(const struct device *dev,
struct i2c_msg *msgs)
{
struct i2c_enhance_data *data = dev->data;
const struct i2c_enhance_config *config = dev->config;
int res;
/* Lock mutex of i2c controller */
k_mutex_lock(&data->mutex, K_FOREVER);
/*
* If the transaction of write to read is divided into two
* transfers, the repeat start transfer uses this flag to
* exclude checking bus busy.
*/
if (data->i2ccs == I2C_CH_NORMAL) {
/* Make sure we're in a good state to start */
if (i2c_bus_not_available(dev)) {
/* Recovery I2C bus */
i2c_recover_bus(dev);
/*
* After resetting I2C bus, if I2C bus is not available
* (No external pull-up), drop the transaction.
*/
if (i2c_bus_not_available(dev)) {
/* Unlock mutex of i2c controller */
k_mutex_unlock(&data->mutex);
return -EIO;
}
}
msgs->flags |= I2C_MSG_START;
}
for (int i = 0; i < num_msgs; i++) {
for (int i = 0; i < data->num_msgs; i++) {
data->widx = 0;
data->ridx = 0;
data->err = 0;
data->msgs = &(msgs[i]);
data->addr_16bit = addr;
if (msgs->flags & I2C_MSG_START) {
data->i2ccs = I2C_CH_NORMAL;
@ -569,6 +586,293 @@ static int i2c_enhance_transfer(const struct device *dev, struct i2c_msg *msgs,
if (data->err || (msgs->flags & I2C_MSG_STOP)) {
data->i2ccs = I2C_CH_NORMAL;
}
return data->err;
}
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
static void enhanced_i2c_set_cmd_addr_regs(const struct device *dev)
{
const struct i2c_enhance_config *config = dev->config;
struct i2c_enhance_data *data = dev->data;
uint32_t dlm_base;
uint8_t *base = config->base;
/* Set "Address Register" to store the I2C data. */
dlm_base = (uint32_t)data->i2c_cq_mode_rx_dlm & 0xffffff;
IT8XXX2_I2C_RAMH2A(base) = (dlm_base >> 16) & 0xff;
IT8XXX2_I2C_RAMHA(base) = (dlm_base >> 8) & 0xff;
IT8XXX2_I2C_RAMLA(base) = dlm_base & 0xff;
/* Set "Command Address Register" to get commands. */
dlm_base = (uint32_t)data->i2c_cq_mode_tx_dlm & 0xffffff;
IT8XXX2_I2C_CMD_ADDH2(base) = (dlm_base >> 16) & 0xff;
IT8XXX2_I2C_CMD_ADDH(base) = (dlm_base >> 8) & 0xff;
IT8XXX2_I2C_CMD_ADDL(base) = dlm_base & 0xff;
}
static void enhanced_i2c_cq_write(const struct device *dev)
{
struct i2c_enhance_data *data = dev->data;
struct i2c_cq_packet *i2c_cq_pckt;
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
/* Set commands in RAM. */
i2c_cq_pckt->id = data->addr_16bit << 1;
i2c_cq_pckt->cmd_l = I2C_CQ_CMD_L_P | I2C_CQ_CMD_L_E | num_bit_2_0;
i2c_cq_pckt->cmd_h = num_bit_10_3;
for (int i = 0; i < data->cq_msgs[0].len; i++) {
i2c_cq_pckt->wdata[i] = data->cq_msgs[0].buf[i];
}
}
static void enhanced_i2c_cq_read(const struct device *dev)
{
struct i2c_enhance_data *data = dev->data;
struct i2c_cq_packet *i2c_cq_pckt;
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
/* Set commands in RAM. */
i2c_cq_pckt->id = data->addr_16bit << 1;
i2c_cq_pckt->cmd_l = I2C_CQ_CMD_L_RW | I2C_CQ_CMD_L_P |
I2C_CQ_CMD_L_E | num_bit_2_0;
i2c_cq_pckt->cmd_h = num_bit_10_3;
}
static void enhanced_i2c_cq_write_to_read(const struct device *dev)
{
struct i2c_enhance_data *data = dev->data;
struct i2c_cq_packet *i2c_cq_pckt;
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
int i;
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
/* Set commands in RAM. (command byte for write) */
i2c_cq_pckt->id = data->addr_16bit << 1;
i2c_cq_pckt->cmd_l = num_bit_2_0;
i2c_cq_pckt->cmd_h = num_bit_10_3;
for (i = 0; i < data->cq_msgs[0].len; i++) {
i2c_cq_pckt->wdata[i] = data->cq_msgs[0].buf[i];
}
/* Set commands in RAM. (command byte for read) */
num_bit_2_0 = (data->cq_msgs[1].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
num_bit_10_3 = ((data->cq_msgs[1].len - 1) >> 3) & 0xff;
i2c_cq_pckt->wdata[i++] = I2C_CQ_CMD_L_RS | I2C_CQ_CMD_L_RW |
I2C_CQ_CMD_L_P | I2C_CQ_CMD_L_E | num_bit_2_0;
i2c_cq_pckt->wdata[i] = num_bit_10_3;
}
static int enhanced_i2c_cq_isr(const struct device *dev)
{
struct i2c_enhance_data *data = dev->data;
const struct i2c_enhance_config *config = dev->config;
uint8_t *base = config->base;
/* Device 1 finish IRQ. */
if (IT8XXX2_I2C_FST(base) & IT8XXX2_I2C_FST_DEV1_IRQ) {
uint8_t msgs_idx = data->num_msgs - 1;
/* Get data if this is a read transaction. */
for (int i = 0; i < data->cq_msgs[msgs_idx].len; i++) {
data->cq_msgs[msgs_idx].buf[i] =
data->i2c_cq_mode_rx_dlm[i];
}
} else {
/* Device 1 error have occurred. eg. nack, timeout... */
if (IT8XXX2_I2C_NST(base) & IT8XXX2_I2C_NST_ID_NACK) {
data->err = E_HOSTA_ACK;
} else {
data->err = IT8XXX2_I2C_STR(base) &
E_HOSTA_ANY_ERROR;
}
}
/* Reset bus. */
IT8XXX2_I2C_CTR(base) = E_STS_AND_HW_RST;
IT8XXX2_I2C_CTR1(base) = 0;
return 0;
}
static int enhanced_i2c_cmd_queue_trans(const struct device *dev)
{
struct i2c_enhance_data *data = dev->data;
const struct i2c_enhance_config *config = dev->config;
uint8_t *base = config->base;
/* State reset and hardware reset. */
IT8XXX2_I2C_CTR(base) = E_STS_AND_HW_RST;
/* Set "PSR" registers to decide the i2c speed. */
i2c_enhanced_port_set_frequency(dev, config->bitrate);
/* Set time out register. port D, E, or F clock/data low timeout. */
IT8XXX2_I2C_TOR(base) = I2C_CLK_LOW_TIMEOUT;
if (data->num_msgs == 2) {
/* I2C write to read of command queue mode. */
enhanced_i2c_cq_write_to_read(dev);
} else {
/* I2C read of command queue mode. */
if (data->cq_msgs[0].flags & I2C_MSG_READ) {
enhanced_i2c_cq_read(dev);
/* I2C write of command queue mode. */
} else {
enhanced_i2c_cq_write(dev);
}
}
/* Enable i2c module with command queue mode. */
IT8XXX2_I2C_CTR1(base) = IT8XXX2_I2C_MDL_EN | IT8XXX2_I2C_COMQ_EN;
/* One shot on device 1. */
IT8XXX2_I2C_MODE_SEL(base) = 0;
IT8XXX2_I2C_CTR2(base) = 1;
/* Start */
chip_block_idle();
IT8XXX2_I2C_CTR(base) = E_START_CQ;
return 1;
}
static int i2c_enhance_cq_transfer(const struct device *dev,
struct i2c_msg *msgs)
{
struct i2c_enhance_data *data = dev->data;
const struct i2c_enhance_config *config = dev->config;
int res = 0;
data->err = 0;
data->cq_msgs = msgs;
/* Start transaction */
if (enhanced_i2c_cmd_queue_trans(dev)) {
/* Enable i2c interrupt */
irq_enable(config->i2c_irq_base);
}
/* Wait for the transfer to complete */
res = k_sem_take(&data->device_sync_sem, K_MSEC(100));
irq_disable(config->i2c_irq_base);
if (res != 0) {
data->err = ETIMEDOUT;
/* Reset i2c port. */
i2c_reset(dev);
LOG_ERR("I2C ch%d:0x%X reset cause %d",
config->port, data->addr_16bit, I2C_RC_TIMEOUT);
}
chip_permit_idle();
return data->err;
}
static bool cq_mode_allowed(const struct device *dev, struct i2c_msg *msgs)
{
struct i2c_enhance_data *data = dev->data;
/*
* When there is only one message, use the command queue transfer
* directly.
*/
if (data->num_msgs == 1 && (msgs[0].flags & I2C_MSG_STOP)) {
/* Read transfer payload too long, use PIO mode */
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) &&
(msgs[0].len > CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE)) {
return false;
}
/* Write transfer payload too long, use PIO mode */
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
(msgs[0].len > I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE)) {
return false;
}
/*
* Write of I2C target address without writing data, used by
* cmd_i2c_scan. Use PIO mode.
*/
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
(msgs[0].len == 0)) {
return false;
}
return true;
}
/*
* When there are two messages, we need to judge whether or not there
* is I2C_MSG_RESTART flag from the second message, and then decide to
* do the command queue or PIO mode transfer.
*/
if (data->num_msgs == 2) {
/*
* The first of two messages must be write.
* If the length of write to read transfer is greater than
* command queue payload size, there will execute PIO mode.
*/
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
(msgs[0].len <= I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE)) {
/*
* The transfer is i2c_burst_read().
*
* e.g. msg[0].flags = I2C_MSG_WRITE;
* msg[1].flags = I2C_MSG_RESTART | I2C_MSG_READ |
* I2C_MSG_STOP;
*/
if ((msgs[1].flags & I2C_MSG_RESTART) &&
((msgs[1].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) &&
(msgs[1].flags & I2C_MSG_STOP) &&
(msgs[1].len <= CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE)) {
return true;
}
}
}
return false;
}
#endif /* CONFIG_I2C_IT8XXX2_CQ_MODE */
static int i2c_enhance_transfer(const struct device *dev,
struct i2c_msg *msgs,
uint8_t num_msgs, uint16_t addr)
{
struct i2c_enhance_data *data = dev->data;
data->num_msgs = num_msgs;
data->addr_16bit = addr;
/* Lock mutex of i2c controller */
k_mutex_lock(&data->mutex, K_FOREVER);
/*
* If the transaction of write to read is divided into two
* transfers, the repeat start transfer uses this flag to
* exclude checking bus busy.
*/
if (data->i2ccs == I2C_CH_NORMAL) {
/* Make sure we're in a good state to start */
if (i2c_bus_not_available(dev)) {
/* Recovery I2C bus */
i2c_recover_bus(dev);
/*
* After resetting I2C bus, if I2C bus is not available
* (No external pull-up), drop the transaction.
*/
if (i2c_bus_not_available(dev)) {
return -EIO;
}
}
}
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
if (cq_mode_allowed(dev, msgs)) {
data->err = i2c_enhance_cq_transfer(dev, msgs);
} else
#endif
{
data->err = i2c_enhance_pio_transfer(dev, msgs);
}
/* Unlock mutex of i2c controller */
k_mutex_unlock(&data->mutex);
@ -581,11 +885,23 @@ static void i2c_enhance_isr(void *arg)
struct i2c_enhance_data *data = dev->data;
const struct i2c_enhance_config *config = dev->config;
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
uint8_t *base = config->base;
/* If done doing work, wake up the task waiting for the transfer */
if (!i2c_transaction(dev)) {
irq_disable(config->i2c_irq_base);
k_sem_give(&data->device_sync_sem);
if (IT8XXX2_I2C_CTR1(base) & IT8XXX2_I2C_COMQ_EN) {
if (enhanced_i2c_cq_isr(dev)) {
return;
}
} else
#endif
{
if (i2c_transaction(dev)) {
return;
}
}
irq_disable(config->i2c_irq_base);
k_sem_give(&data->device_sync_sem);
}
static int i2c_enhance_init(const struct device *dev)
@ -615,6 +931,11 @@ static int i2c_enhance_init(const struct device *dev)
/* bit1, Module enable */
IT8XXX2_I2C_CTR1(base) = 0;
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
/* Set command address registers. */
enhanced_i2c_set_cmd_addr_regs(dev);
#endif
/* Set clock frequency for I2C ports */
if (config->bitrate == I2C_BITRATE_STANDARD ||
config->bitrate == I2C_BITRATE_FAST ||

View file

@ -1696,7 +1696,15 @@ enum chip_pll_mode {
#define IT8XXX2_I2C_IRQ_ST(base) ECREG(base + 0x0D)
#define IT8XXX2_I2C_IDR(base) ECREG(base + 0x06)
#define IT8XXX2_I2C_TOS(base) ECREG(base + 0x07)
#define IT8XXX2_I2C_STR2(base) ECREG(base + 0x12)
#define IT8XXX2_I2C_NST(base) ECREG(base + 0x13)
#define IT8XXX2_I2C_TO_ARB_ST(base) ECREG(base + 0x18)
#define IT8XXX2_I2C_ERR_ST(base) ECREG(base + 0x19)
#define IT8XXX2_I2C_FST(base) ECREG(base + 0x1B)
#define IT8XXX2_I2C_EM(base) ECREG(base + 0x1C)
#define IT8XXX2_I2C_MODE_SEL(base) ECREG(base + 0x1D)
#define IT8XXX2_I2C_IDR2(base) ECREG(base + 0x1F)
#define IT8XXX2_I2C_CTR2(base) ECREG(base + 0x20)
#define IT8XXX2_I2C_RAMHA(base) ECREG(base + 0x23)
#define IT8XXX2_I2C_RAMLA(base) ECREG(base + 0x24)
#define IT8XXX2_I2C_RAMHA2(base) ECREG(base + 0x2B)
@ -1737,7 +1745,17 @@ enum chip_pll_mode {
#define IT8XXX2_I2C_SCL_IN BIT(2)
#define IT8XXX2_I2C_SDA_IN BIT(0)
/* 0x0A: Control 1 */
#define IT8XXX2_I2C_COMQ_EN BIT(7)
#define IT8XXX2_I2C_MDL_EN BIT(1)
/* 0x13: Nack Status */
#define IT8XXX2_I2C_NST_CNS BIT(7)
#define IT8XXX2_I2C_NST_ID_NACK BIT(3)
/* 0x19: Error Status */
#define IT8XXX2_I2C_ERR_ST_DEV1_EIRQ BIT(0)
/* 0x1B: Finish Status */
#define IT8XXX2_I2C_FST_DEV1_IRQ BIT(4)
/* 0x1C: Error Mask */
#define IT8XXX2_I2C_EM_DEV1_IRQ BIT(4)
/* --- General Control (GCTRL) --- */
#define IT83XX_GCTRL_BASE 0x00F02000

View file

@ -57,6 +57,12 @@ uint32_t chip_get_pll_freq(void);
void chip_pll_ctrl(enum chip_pll_mode mode);
void riscv_idle(enum chip_pll_mode mode, unsigned int key);
#ifdef CONFIG_SOC_IT8XXX2_CPU_IDLE_GATING
void chip_permit_idle(void);
void chip_block_idle(void);
bool cpu_idle_not_allowed(void);
#endif
#endif /* !_ASMLANGUAGE */
#endif /* __SOC_COMMON_H_ */

View file

@ -59,6 +59,13 @@ config SOC_IT8XXX2_GPIO_H7_DEFAULT_OUTPUT_LOW
capability to pull it down. We can only set it as output low,
so we enable output low for it at initialization to prevent leakage.
config SOC_IT8XXX2_CPU_IDLE_GATING
bool
help
This option determines whether the entering CPU idle mode can be
gated by individual drivers. When this option is disabled, CPU idle
mode is always permitted.
choice
prompt "Clock source for PLL reference clock"

View file

@ -176,6 +176,26 @@ BUILD_ASSERT(CONFIG_FLASH_INIT_PRIORITY < CONFIG_IT8XXX2_PLL_SEQUENCE_PRIORITY,
"CONFIG_FLASH_INIT_PRIORITY must be less than CONFIG_IT8XXX2_PLL_SEQUENCE_PRIORITY");
#endif /* CONFIG_SOC_IT8XXX2_PLL_FLASH_48M */
#ifdef CONFIG_SOC_IT8XXX2_CPU_IDLE_GATING
/* Preventing CPU going into idle mode during command queue. */
static atomic_t cpu_idle_disabled;
void chip_permit_idle(void)
{
atomic_dec(&cpu_idle_disabled);
}
void chip_block_idle(void)
{
atomic_inc(&cpu_idle_disabled);
}
bool cpu_idle_not_allowed(void)
{
return !!(atomic_get(&cpu_idle_disabled));
}
#endif
/* The routine must be called with interrupts locked */
void riscv_idle(enum chip_pll_mode mode, unsigned int key)
{
@ -214,7 +234,20 @@ void riscv_idle(enum chip_pll_mode mode, unsigned int key)
void arch_cpu_idle(void)
{
riscv_idle(CHIP_PLL_DOZE, MSTATUS_IEN);
#ifdef CONFIG_SOC_IT8XXX2_CPU_IDLE_GATING
/*
* The EC processor(CPU) cannot be in the k_cpu_idle() during
* the transactions with the CQ mode(DMA mode). Otherwise,
* the EC processor would be clock gated.
*/
if (cpu_idle_not_allowed()) {
/* Restore global interrupt lockout state */
irq_unlock(MSTATUS_IEN);
} else
#endif
{
riscv_idle(CHIP_PLL_DOZE, MSTATUS_IEN);
}
}
void arch_cpu_atomic_idle(unsigned int key)