Files
OpenCellular/chip/it83xx/peci.c
Dino Li 60e110acd1 it8380dev: add i2c control module
Add i2c control module for emulation board.
To rename CONFIG_ to CONFIG_IT83XX_ for IT83XX series configuration.

Signed-off-by: Dino Li <dino.li@ite.com.tw>

BRANCH=none
BUG=none
TEST=1. console command "i2cscan" found devices correctly.
     2. console command "i2cxfer".
         2-a. port2 + battery, i2cxfer r, r16, and rlen OK.
         2-b. port1 + slave evb, i2cxfer r, r16, rlen, w, and w16 OK.

Change-Id: I67165f7dcdef538ba6dd03b47f1621a73cc68379
Reviewed-on: https://chromium-review.googlesource.com/263678
Reviewed-by: Alec Berg <alecaberg@chromium.org>
Commit-Queue: Dino Li <dino.li@ite.com.tw>
Tested-by: Dino Li <dino.li@ite.com.tw>
2015-06-30 12:22:02 +00:00

430 lines
10 KiB
C

/* 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_IT83XX_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);