diff options
Diffstat (limited to 'chip/it83xx/peci.c')
-rw-r--r-- | chip/it83xx/peci.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/chip/it83xx/peci.c b/chip/it83xx/peci.c new file mode 100644 index 0000000000..b613fa7971 --- /dev/null +++ b/chip/it83xx/peci.c @@ -0,0 +1,429 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* PECI interface for Chrome EC */ + +#include "chipset.h" +#include "clock.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "peci.h" +#include "registers.h" +#include "temp_sensor.h" +#include "util.h" +#include "timer.h" +#include "task.h" + +#define TEMP_AVG_LENGTH 4 /* Should be power of 2 */ +static int temp_vals[TEMP_AVG_LENGTH]; +static int temp_idx; + +#define PECI_TARGET_ADDRESS 0x30 +#define PECI_WRITE_DATA_FIFO_SIZE 15 +#define PECI_READ_DATA_FIFO_SIZE 16 + +#define PECI_GET_TEMP_READ_LENGTH 2 +#define PECI_GET_TEMP_WRITE_LENGTH 0 +#define PECI_GET_TEMP_TIMEOUT_US 200 + +/* PECI Command Code */ +enum peci_command_code { + PECI_CMD_PING = 0x00, + PECI_CMD_GET_DIB = 0xF7, + PECI_CMD_GET_TEMP = 0x01, + PECI_CMD_RD_PKG_CFG = 0xA1, + PECI_CMD_WR_PKG_CFG = 0xA5, + PECI_CMD_RD_IAMSR = 0xB1, + PECI_CMD_WR_IAMSR = 0xB5, + PECI_CMD_RD_PCI_CFG = 0x61, + PECI_CMD_WR_PCI_CFG = 0x65, + PECI_CMD_RD_PCI_CFG_LOCAL = 0xE1, + PECI_CMD_WR_PCI_CFG_LOCAL = 0xE5, +}; + +enum peci_status { + PECI_STATUS_NO_ERR = 0x00, + PECI_STATUS_HOBY = 0x01, + PECI_STATUS_FINISH = 0x02, + PECI_STATUS_RD_FCS_ERR = 0x04, + PECI_STATUS_WR_FCS_ERR = 0x08, + PECI_STATUS_EXTERR = 0x20, + PECI_STATUS_BUSERR = 0x40, + PECI_STATUS_RCV_ERRCODE = 0x80, + PECI_STATUS_ERR_NEED_RST = (PECI_STATUS_BUSERR | PECI_STATUS_EXTERR), + PECI_STATUS_ANY_ERR = (PECI_STATUS_RCV_ERRCODE | + PECI_STATUS_BUSERR | + PECI_STATUS_EXTERR | + PECI_STATUS_WR_FCS_ERR | + PECI_STATUS_RD_FCS_ERR), + PECI_STATUS_ANY_BIT = 0xFE, + PECI_STATUS_TIMEOUT = 0xFF, +}; + +static task_id_t peci_current_task; + +static void peci_init_vtt_freq(void) +{ + /* + * bit2, enable the PECI interrupt generated by data valid event + * from PECI. + * + * bit[1-0], these bits are used to set PECI VTT level. + * 00b: 1.10v + * 01b: 1.05v + * 10b: 1.00v + */ + IT83XX_PECI_PADCTLR = 0x06; + + /* + * bit[2-0], these bits are used to set PECI host's optimal + * transfer rate. + * 000b: 2.0 MHz + * 001b: 1.0 MHz + * 100b: 1.6 MHz + */ + IT83XX_PECI_HOCTL2R = 0x01; +} + +static void peci_reset(void) +{ + /* Reset PECI */ + IT83XX_GCTRL_RSTC4 |= 0x10; + + /* short delay */ + udelay(15); + + peci_init_vtt_freq(); +} + +/** + * Start a PECI transaction + * + * @param addr client address + * @param w_len write length (no include [Cmd Code] and [AW FCS]) + * @param r_len read length (no include [FCS]) + * @param cmd_code command code + * @param *w_buf How buffer pointer of write data + * @param *r_buf How buffer pointer of read data + * @param timeout_us transaction timeout unit:us + * + * @return zero if successful, non-zero if error + */ +static enum peci_status peci_transaction(uint8_t addr, + uint8_t w_len, + uint8_t r_len, + enum peci_command_code cmd_code, + uint8_t *w_buf, + uint8_t *r_buf, + int timeout_us) +{ + uint8_t status; + int index; + + /* + * bit5, Both write and read data FIFO pointers will be cleared. + * + * bit4, This bit enables the PECI host to abort the transaction + * when FCS error occurs. + * + * bit2, This bit enables the contention mechanism of the PECI bus. + * When this bit is set, the host will abort the transaction + * if the PECI bus is contentious. + */ + IT83XX_PECI_HOCTLR |= 0x34; + + /* This register is the target address field of the PECI protocol. */ + IT83XX_PECI_HOTRADDR = addr; + + /* This register is the write length field of the PECI protocol. */ + ASSERT(w_len <= PECI_WRITE_DATA_FIFO_SIZE); + + if (cmd_code == PECI_CMD_PING) { + /* write length is 0 */ + IT83XX_PECI_HOWRLR = 0x00; + } else { + if ((cmd_code == PECI_CMD_WR_PKG_CFG) || + (cmd_code == PECI_CMD_WR_IAMSR) || + (cmd_code == PECI_CMD_WR_PCI_CFG) || + (cmd_code == PECI_CMD_WR_PCI_CFG_LOCAL)) { + + /* write length include Cmd Code + AW FCS */ + IT83XX_PECI_HOWRLR = w_len + 2; + + /* bit1, The bit enables the AW_FCS hardwired mechanism + * based on the PECI command. This bit is functional + * only when the AW_FCS supported command of + * PECI 2.0/3.0/3.1 is issued. + * When this bit is set, the hardware will handle the + * calculation of AW_FCS. + */ + IT83XX_PECI_HOCTLR |= 0x02; + } else { + /* write length include Cmd Code */ + IT83XX_PECI_HOWRLR = w_len + 1; + + IT83XX_PECI_HOCTLR &= ~0x02; + } + } + + /* This register is the read length field of the PECI protocol. */ + ASSERT(r_len <= PECI_READ_DATA_FIFO_SIZE); + IT83XX_PECI_HORDLR = r_len; + + /* This register is the command field of the PECI protocol. */ + IT83XX_PECI_HOCMDR = cmd_code; + + /* The write data field of the PECI protocol. */ + for (index = 0x00; index < w_len; index++) + IT83XX_PECI_HOWRDR = w_buf[index]; + + peci_current_task = task_get_current(); +#ifdef CONFIG_PECI_WITH_INTERRUPT + task_clear_pending_irq(IT83XX_IRQ_PECI); + task_enable_irq(IT83XX_IRQ_PECI); + + /* start */ + IT83XX_PECI_HOCTLR |= 0x01; + + /* pre-set timeout */ + index = timeout_us; + if (task_wait_event(timeout_us) != TASK_EVENT_TIMER) + index = 0; + + task_disable_irq(IT83XX_IRQ_PECI); +#else + /* start */ + IT83XX_PECI_HOCTLR |= 0x01; + + for (index = 0x00; index < timeout_us; index += 16) { + if (IT83XX_PECI_HOSTAR & PECI_STATUS_ANY_BIT) + break; + + udelay(15); + } +#endif + peci_current_task = TASK_ID_INVALID; + + if (index < timeout_us) { + + status = IT83XX_PECI_HOSTAR; + + /* any error */ + if (IT83XX_PECI_HOSTAR & PECI_STATUS_ANY_ERR) { + + if (IT83XX_PECI_HOSTAR & PECI_STATUS_ERR_NEED_RST) + peci_reset(); + + } else if (IT83XX_PECI_HOSTAR & PECI_STATUS_FINISH) { + + /* The read data field of the PECI protocol. */ + for (index = 0x00; index < r_len; index++) + r_buf[index] = IT83XX_PECI_HORDDR; + + /* W/C */ + IT83XX_PECI_HOSTAR = PECI_STATUS_FINISH; + status = IT83XX_PECI_HOSTAR; + } + } else { + /* transaction timeout */ + status = PECI_STATUS_TIMEOUT; + } + + /* Don't disable PECI host controller if controller already enable. */ + IT83XX_PECI_HOCTLR = 0x08; + + /* W/C */ + IT83XX_PECI_HOSTAR = PECI_STATUS_ANY_BIT; + + return status; +} + +int peci_get_cpu_temp(void) +{ + uint8_t r_buf[PECI_GET_TEMP_READ_LENGTH] = {0}; + int cpu_temp = -1; + + if (peci_transaction(PECI_TARGET_ADDRESS, + PECI_GET_TEMP_WRITE_LENGTH, + PECI_GET_TEMP_READ_LENGTH, + PECI_CMD_GET_TEMP, + NULL, + r_buf, + PECI_GET_TEMP_TIMEOUT_US) == + PECI_STATUS_NO_ERR) { + + /* Get relative raw data of temperature. */ + cpu_temp = (r_buf[1] << 8) | r_buf[0]; + +#ifdef CONFIG_PECI_TJMAX + /* Convert relative raw data to degrees C. */ + cpu_temp = ((cpu_temp ^ 0xFFFF) + 1) >> 6; + /* temperature in K */ + cpu_temp = (CONFIG_PECI_TJMAX - cpu_temp) + 273; +#endif + } + + return cpu_temp; +} + +int peci_temp_sensor_get_val(int idx, int *temp_ptr) +{ + int sum = 0; + int success_cnt = 0; + int i; + + if (!chipset_in_state(CHIPSET_STATE_ON)) + return EC_ERROR_NOT_POWERED; + + for (i = 0; i < TEMP_AVG_LENGTH; ++i) { + if (temp_vals[i] >= 0) { + success_cnt++; + sum += temp_vals[i]; + } + } + + /* + * Require at least two valid samples. When the AP transitions into S0, + * it is possible, depending on the timing of the PECI sample, to read + * an invalid temperature. This is very rare, but when it does happen + * the temperature returned is CONFIG_PECI_TJMAX. Requiring two valid + * samples here assures us that one bad maximum temperature reading + * when entering S0 won't cause us to trigger an over temperature. + */ + if (success_cnt < 2) + return EC_ERROR_UNKNOWN; + + *temp_ptr = sum / success_cnt; + return EC_SUCCESS; +} + +static void peci_temp_sensor_poll(void) +{ + temp_vals[temp_idx] = peci_get_cpu_temp(); + temp_idx = (temp_idx + 1) & (TEMP_AVG_LENGTH - 1); +} +DECLARE_HOOK(HOOK_TICK, peci_temp_sensor_poll, HOOK_PRIO_TEMP_SENSOR); + +void peci_interrupt(void) +{ + task_clear_pending_irq(IT83XX_IRQ_PECI); + task_disable_irq(IT83XX_IRQ_PECI); + + if (peci_current_task != TASK_ID_INVALID) + task_wake(peci_current_task); +} + +static void peci_init(void) +{ + int i; + + peci_init_vtt_freq(); + + /* bit3,this bit enables the PECI host controller. */ + IT83XX_PECI_HOCTLR |= 0x08; + + /* To enable PECI function pin */ + IT83XX_GPIO_GPCRF6 = 0x00; + + /* bit4, PECI enable */ + IT83XX_GPIO_GRC2 |= 0x10; + + /* Initialize temperature reading buffer to a sane value. */ + for (i = 0; i < TEMP_AVG_LENGTH; ++i) + temp_vals[i] = 300; /* 27 C */ +} +DECLARE_HOOK(HOOK_INIT, peci_init, HOOK_PRIO_DEFAULT); + +/*****************************************************************************/ +/* Console commands */ + +static int peci_cmd(int argc, char **argv) +{ + uint8_t r_buf[PECI_READ_DATA_FIFO_SIZE] = {0}; + uint8_t w_buf[PECI_WRITE_DATA_FIFO_SIZE] = {0}; + + int addr, wlen, rlen, cmd, time_us, param; + char *e; + + if ((argc < 6) || (argc > 8)) + return EC_ERROR_PARAM_COUNT; + + addr = strtoi(argv[1], &e, 0); + if (*e) + return EC_ERROR_PARAM1; + + wlen = strtoi(argv[2], &e, 0); + if (*e) + return EC_ERROR_PARAM2; + + rlen = strtoi(argv[3], &e, 0); + if (*e) + return EC_ERROR_PARAM3; + + cmd = strtoi(argv[4], &e, 0); + if (*e) + return EC_ERROR_PARAM4; + + time_us = strtoi(argv[5], &e, 0); + if (*e) + return EC_ERROR_PARAM5; + + if (argc > 6) { + param = strtoi(argv[6], &e, 0); + if (*e) + return EC_ERROR_PARAM6; + + /* MSB of parameter */ + w_buf[3] = (uint8_t)(param >> 24); + /* LSB of parameter */ + w_buf[2] = (uint8_t)(param >> 16); + /* Index */ + w_buf[1] = (uint8_t)(param >> 8); + /* Host ID[7:1] & Retry[0] */ + w_buf[0] = (uint8_t)(param >> 0); + + if (argc > 7) { + param = strtoi(argv[7], &e, 0); + if (*e) + return EC_ERROR_PARAM7; + + /* Data (1, 2 or 4 bytes) */ + w_buf[7] = (uint8_t)(param >> 24); + w_buf[6] = (uint8_t)(param >> 16); + w_buf[5] = (uint8_t)(param >> 8); + w_buf[4] = (uint8_t)(param >> 0); + } + } else { + wlen = 0x00; + } + + if (peci_transaction(addr, wlen, rlen, cmd, w_buf, r_buf, time_us)) { + ccprintf("PECI transaction error\n"); + return EC_ERROR_UNKNOWN; + } + ccprintf("PECI read data: %.*h\n", rlen, r_buf); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(peci, peci_cmd, + "addr wlen rlen cmd timeout(us)", + "PECI command", + NULL); + +static int command_peci_temp(int argc, char **argv) +{ + int t = peci_get_cpu_temp(); + if (t == -1) { + ccprintf("PECI get cpu temp error\n"); + return EC_ERROR_UNKNOWN; + } + ccprintf("CPU temp = %d K = %d C\n", t, K_TO_C(t)); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(pecitemp, command_peci_temp, + NULL, + "Print CPU temperature", + NULL); |