blob: ee94f2baaccbaf8c89a37b6a23b20d454169f7a8 [file] [log] [blame]
/* Copyright 2015 The ChromiumOS Authors
* 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 "builtin/assert.h"
#include "clock.h"
#include "hooks.h"
#include "peci.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
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 peci transaction data
*
* @return zero if successful, non-zero if error
*/
int peci_transaction(struct peci_data *peci)
{
uint8_t status;
int index;
/* To enable PECI function pin */
IT83XX_GPIO_GPCRF6 = 0x00;
/*
* 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 = peci->addr;
/* This register is the write length field of the PECI protocol. */
ASSERT(peci->w_len <= PECI_WRITE_DATA_FIFO_SIZE);
if (peci->cmd_code == PECI_CMD_PING) {
/* write length is 0 */
IT83XX_PECI_HOWRLR = 0x00;
} else {
if ((peci->cmd_code == PECI_CMD_WR_PKG_CFG) ||
(peci->cmd_code == PECI_CMD_WR_IAMSR) ||
(peci->cmd_code == PECI_CMD_WR_PCI_CFG) ||
(peci->cmd_code == PECI_CMD_WR_PCI_CFG_LOCAL)) {
/* write length include Cmd Code + AW FCS */
IT83XX_PECI_HOWRLR = peci->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 = peci->w_len + 1;
IT83XX_PECI_HOCTLR &= ~0x02;
}
}
/* This register is the read length field of the PECI protocol. */
ASSERT(peci->r_len <= PECI_READ_DATA_FIFO_SIZE);
IT83XX_PECI_HORDLR = peci->r_len;
/* This register is the command field of the PECI protocol. */
IT83XX_PECI_HOCMDR = peci->cmd_code;
/* The write data field of the PECI protocol. */
for (index = 0x00; index < peci->w_len; index++)
IT83XX_PECI_HOWRDR = peci->w_buf[index];
peci_current_task = task_get_current();
task_clear_pending_irq(IT83XX_IRQ_PECI);
task_enable_irq(IT83XX_IRQ_PECI);
/* start */
IT83XX_PECI_HOCTLR |= 0x01;
/* pre-set timeout */
index = peci->timeout_us;
if (task_wait_event(peci->timeout_us) != TASK_EVENT_TIMER)
index = 0;
task_disable_irq(IT83XX_IRQ_PECI);
peci_current_task = TASK_ID_INVALID;
if (index < peci->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 < peci->r_len; index++)
peci->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;
/* Disable PECI function pin */
IT83XX_GPIO_GPCRF6 = 0x80;
return status;
}
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)
{
clock_enable_peripheral(CGC_OFFSET_PECI, 0, 0);
peci_init_vtt_freq();
/* bit3,this bit enables the PECI host controller. */
IT83XX_PECI_HOCTLR |= 0x08;
/* bit4, PECI enable */
IT83XX_GPIO_GRC2 |= 0x10;
}
DECLARE_HOOK(HOOK_INIT, peci_init, HOOK_PRIO_DEFAULT);