/* Copyright 2014 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 "chipset.h" #include "clock.h" #include "clock_chip.h" #include "common.h" #include "console.h" #include "gpio.h" #include "hooks.h" #include "peci.h" #include "registers.h" #include "task.h" #include "temp_sensor.h" #include "timer.h" #include "util.h" /* Initial PECI baud rate */ #define PECI_BAUD_RATE 750000 #define TEMP_AVG_LENGTH 4 /* Should be power of 2 */ /* PECI Time-out */ #define PECI_DONE_TIMEOUT_US (10 * MSEC) #define NULL_PENDING_TASK_ID 0xFFFFFFFF #define PECI_MAX_FIFO_SIZE 16 #define PROC_SOCKET 0x30 /* PECI Command Code */ enum peci_command_t { PECI_COMMAND_PING = 0x00, PECI_COMMAND_GET_DIB = 0xF7, PECI_COMMAND_GET_TEMP = 0x01, PECI_COMMAND_RD_PKG_CFG = 0xA1, PECI_COMMAND_WR_PKG_CFG = 0xA5, PECI_COMMAND_RD_IAMSR = 0xB1, PECI_COMMAND_RD_PCI_CFG = 0x61, PECI_COMMAND_RD_PCI_CFG_LOCAL = 0xE1, PECI_COMMAND_WR_PCI_CFG_LOCAL = 0xE5, PECI_COMMAND_NONE = 0xFF }; #define PECI_COMMAND_GET_TEMP_WR_LENS 0x00 #define PECI_COMMAND_GET_TEMP_RD_LENS 0x02 /* PECI Domain Number */ static int temp_vals[TEMP_AVG_LENGTH]; static int temp_idx; static uint8_t peci_sts; /* For PECI Done interrupt usage */ static int peci_pending_task_id; /*****************************************************************************/ /* Internal functions */ /** * This routine initiates the parameters of a PECI transaction * * @param wr_length How many byte of *wr_data went to be send * @param rd_length How many byte went to received (not include FCS) * @param cmd_code Command code * @param *wr_data Buffer pointer of write data * @return TASK_EVENT_PECI_DONE that mean slave had a response */ static uint32_t peci_trans(uint8_t wr_length, uint8_t rd_length, enum peci_command_t cmd_code, uint8_t *wr_data) { uint32_t events; /* Ensure no PECI transaction is in progress */ if (IS_BIT_SET(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_START_BUSY)) { /* * PECI transaction is in progress - * can not initiate a new one */ return 0; } /* Set basic transaction parameters */ NPCX_PECI_ADDR = PROC_SOCKET; NPCX_PECI_CMD = cmd_code; /* Aviod over space */ if (rd_length > PECI_MAX_FIFO_SIZE) rd_length = PECI_MAX_FIFO_SIZE; /* Read-Length */ NPCX_PECI_RD_LENGTH = rd_length; if (wr_length > PECI_MAX_FIFO_SIZE) wr_length = PECI_MAX_FIFO_SIZE; /* copy of data */ for (events = 0; events < wr_length; events++) NPCX_PECI_DATA_OUT(events) = wr_data[events]; /* Write-Length */ if (cmd_code != PECI_COMMAND_PING) { if ((cmd_code == PECI_COMMAND_WR_PKG_CFG) || (cmd_code == PECI_COMMAND_WR_PCI_CFG_LOCAL)) { /*CMD+AWFCS*/ NPCX_PECI_WR_LENGTH = wr_length + 2; /* Enable AWFCS */ SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_AWFCS_EN); } else { /*CMD*/ NPCX_PECI_WR_LENGTH = wr_length + 1; /* Enable AWFCS */ CLEAR_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_AWFCS_EN); } } /* Start the PECI transaction */ SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_START_BUSY); /* It should be using a interrupt , don't waste cpu computing power */ peci_pending_task_id = task_get_current(); return task_wait_event_mask(TASK_EVENT_PECI_DONE, PECI_DONE_TIMEOUT_US); } /** * PECI transaction error status. * * @return Bit3 - CRC error Bit4 - ABRT error */ static uint8_t peci_check_error_state(void) { return peci_sts; } /*****************************************************************************/ /* PECI drivers */ int peci_get_cpu_temp(void) { uint32_t events; int16_t cpu_temp = -1; /* Start PECI trans */ events = peci_trans(PECI_COMMAND_GET_TEMP_WR_LENS, PECI_COMMAND_GET_TEMP_RD_LENS, PECI_COMMAND_GET_TEMP, NULL); /* if return DONE , that mean slave had a PECI response */ if ((events & TASK_EVENT_PECI_DONE) == TASK_EVENT_PECI_DONE) { /* check CRC & ABRT */ events = peci_check_error_state(); if (events) { ; } else { uint16_t *ptr; ptr = (uint16_t *)&cpu_temp; ptr[0] = (NPCX_PECI_DATA_IN(1) << 8) | (NPCX_PECI_DATA_IN(0) << 0); } } return (int)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) { int val; val = peci_get_cpu_temp(); if (val != -1) { temp_vals[temp_idx] = val; temp_idx = (temp_idx + 1) & (TEMP_AVG_LENGTH - 1); } } DECLARE_HOOK(HOOK_TICK, peci_temp_sensor_poll, HOOK_PRIO_TEMP_SENSOR); static void peci_freq_changed(void) { /* PECI's clock source is FMCLK */ int freq = clock_get_fm_freq(); int baud = 0xF; /* Disable polling while reconfiguring */ NPCX_PECI_CTL_STS = 0; /* * Set the maximum bit rate used by the PECI module during both * Address Timing Negotiation and Data Timing Negotiation. * The resulting maximum bit rate MAX_BIT_RATE in decimal is * according to the following formula: * * MAX_BIT_RATE [d] = (freq / (4 * baudrate)) - 1 * Maximum bit rate should not extend the field's boundaries. */ if (freq != 0) { baud = (uint8_t)(freq / (4 * PECI_BAUD_RATE)) - 1; /* Set maximum PECI baud rate (bit0 - bit4) */ if (baud > 0x1F) baud = 0x1F; } /* Enhanced High-Speed */ if (baud >= 7) { CLEAR_BIT(NPCX_PECI_RATE, 6); CLEAR_BIT(NPCX_PECI_CFG, 3); } else { SET_BIT(NPCX_PECI_RATE, 6); SET_BIT(NPCX_PECI_CFG, 3); } /* Setting Rate */ NPCX_PECI_RATE = baud; } DECLARE_HOOK(HOOK_FREQ_CHANGE, peci_freq_changed, HOOK_PRIO_DEFAULT); static void peci_init(void) { int i; /* Enable clock for PECI peripheral */ clock_enable_peripheral(CGC_OFFSET_PECI, CGC_PECI_MASK, CGC_MODE_RUN | CGC_MODE_SLEEP); /* Set PECI freq */ peci_freq_changed(); /* make sure PECI_DATA function pin enable */ CLEAR_BIT(NPCX_DEVALT(0x0A), 6); /* Set initial clock frequency */ peci_freq_changed(); /* Initialize temperature reading buffer to a valid value. */ for (i = 0; i < TEMP_AVG_LENGTH; ++i) temp_vals[i] = 300; /* 27 C */ /* init Pending task id */ peci_pending_task_id = NULL_PENDING_TASK_ID; /* Enable PECI Done interrupt */ SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_DONE_EN); task_enable_irq(NPCX_IRQ_PECI); } DECLARE_HOOK(HOOK_INIT, peci_init, HOOK_PRIO_DEFAULT); /* If received a PECI DONE interrupt, post the event to PECI task */ static void peci_done_interrupt(void) { if (peci_pending_task_id != NULL_PENDING_TASK_ID) task_set_event(peci_pending_task_id, TASK_EVENT_PECI_DONE); peci_sts = NPCX_PECI_CTL_STS & 0x18; /* no matter what, clear status bit again */ SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_DONE); SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_CRC_ERR); SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_ABRT_ERR); } DECLARE_IRQ(NPCX_IRQ_PECI, peci_done_interrupt, 4); /*****************************************************************************/ /* Console commands */ static int command_peci_temp(int argc, const char **argv) { int t = peci_get_cpu_temp(); if (t == -1) { ccprintf("PECI response timeout\n"); return EC_ERROR_UNKNOWN; } ccprintf("CPU temp = %d K = %d\n", t, K_TO_C(t)); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(pecitemp, command_peci_temp, NULL, "Print CPU temperature");