mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-17 10:31:31 +00:00
The factory tests relies on being able to read CEC_IN through the GPIO API. When it is configured as TA1, it can't be read as a GPIO. With this change, the pin will be a reconfigured as a GPIO at boot or when CEC is runtime disabled using "ectool cec set enable 0" Signed-off-by: Stefan Adolfsson <sadolfsson@chromium.org> BUG=b:79842676 BRANCH=none TEST=Test that "ectool cec read" still works with CEC on, and that "ectool gpioget CEC_IN" reflects the incoming voltage when CEC is off. Change-Id: I3b17d6551612a156897d95ea2473e4fbcbd70e39 Reviewed-on: https://chromium-review.googlesource.com/1064110 Commit-Ready: Stefan Adolfsson <sadolfsson@chromium.org> Tested-by: Stefan Adolfsson <sadolfsson@chromium.org> Reviewed-by: Stefan Adolfsson <sadolfsson@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Jett Rink <jettrink@chromium.org>
1188 lines
31 KiB
C
1188 lines
31 KiB
C
/* Copyright 2018 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.
|
|
*/
|
|
|
|
#include "atomic.h"
|
|
#include "clock_chip.h"
|
|
#include "console.h"
|
|
#include "ec_commands.h"
|
|
#include "fan_chip.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "mkbp_event.h"
|
|
#include "registers.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
#if !(DEBUG_CEC)
|
|
#define CPRINTF(...)
|
|
#define CPRINTS(...)
|
|
#else
|
|
#define CPRINTF(format, args...) cprintf(CC_CEC, format, ## args)
|
|
#define CPRINTS(format, args...) cprints(CC_CEC, format, ## args)
|
|
#endif
|
|
|
|
/* Time in us to timer clock ticks */
|
|
#define APB1_TICKS(t) ((t) * apb1_freq_div_10k / 100)
|
|
#if DEBUG_CEC
|
|
/* Timer clock ticks to us */
|
|
#define APB1_US(ticks) (100*(ticks)/apb1_freq_div_10k)
|
|
#endif
|
|
|
|
/* Notification from interrupt to CEC task that data has been received */
|
|
#define TASK_EVENT_RECEIVED_DATA TASK_EVENT_CUSTOM(1 << 0)
|
|
|
|
/* CEC broadcast address. Also the highest possible CEC address */
|
|
#define CEC_BROADCAST_ADDR 15
|
|
|
|
/* Address to indicate that no logical address has been set */
|
|
#define CEC_UNREGISTERED_ADDR 255
|
|
|
|
/*
|
|
* The CEC specification requires at least one and a maximum of
|
|
* five resends attempts
|
|
*/
|
|
#define CEC_MAX_RESENDS 5
|
|
|
|
/* Size of circular buffer used to store incoming CEC messages */
|
|
#define CEC_CIRCBUF_SIZE 20
|
|
#if CEC_CIRCBUF_SIZE < MAX_CEC_MSG_LEN + 1
|
|
#error "Buffer must fit at least a CEC message and a length byte"
|
|
#endif
|
|
#if CEC_CIRCBUF_SIZE > 255
|
|
#error "Buffer size must not exceed 255 since offsets are uint8_t"
|
|
#endif
|
|
|
|
/*
|
|
* Free time timing (us). Our free-time is calculated from the end of
|
|
* the last bit (not from the start). We compensate by having one
|
|
* free-time period less than in the spec.
|
|
*/
|
|
#define NOMINAL_BIT_TICKS APB1_TICKS(2400)
|
|
/* Resend */
|
|
#define FREE_TIME_RS_TICKS (2 * (NOMINAL_BIT_TICKS))
|
|
/* New initiator */
|
|
#define FREE_TIME_NI_TICKS (4 * (NOMINAL_BIT_TICKS))
|
|
/* Present initiator */
|
|
#define FREE_TIME_PI_TICKS (6 * (NOMINAL_BIT_TICKS))
|
|
|
|
/* Start bit timing */
|
|
#define START_BIT_LOW_TICKS APB1_TICKS(3700)
|
|
#define START_BIT_MIN_LOW_TICKS APB1_TICKS(3500)
|
|
#define START_BIT_MAX_LOW_TICKS APB1_TICKS(3900)
|
|
#define START_BIT_HIGH_TICKS APB1_TICKS(800)
|
|
#define START_BIT_MIN_DURATION_TICKS APB1_TICKS(4300)
|
|
#define START_BIT_MAX_DURATION_TICKS APB1_TICKS(5700)
|
|
|
|
/* Data bit timing */
|
|
#define DATA_ZERO_LOW_TICKS APB1_TICKS(1500)
|
|
#define DATA_ZERO_MIN_LOW_TICKS APB1_TICKS(1300)
|
|
#define DATA_ZERO_MAX_LOW_TICKS APB1_TICKS(1700)
|
|
#define DATA_ZERO_HIGH_TICKS APB1_TICKS(900)
|
|
#define DATA_ZERO_MIN_DURATION_TICKS APB1_TICKS(2050)
|
|
#define DATA_ZERO_MAX_DURATION_TICKS APB1_TICKS(2750)
|
|
|
|
#define DATA_ONE_LOW_TICKS APB1_TICKS(600)
|
|
#define DATA_ONE_MIN_LOW_TICKS APB1_TICKS(400)
|
|
#define DATA_ONE_MAX_LOW_TICKS APB1_TICKS(800)
|
|
#define DATA_ONE_HIGH_TICKS APB1_TICKS(1800)
|
|
#define DATA_ONE_MIN_DURATION_TICKS APB1_TICKS(2050)
|
|
#define DATA_ONE_MAX_DURATION_TICKS APB1_TICKS(2750)
|
|
|
|
/* Time from low that it should be safe to sample an ACK */
|
|
#define NOMINAL_SAMPLE_TIME_TICKS APB1_TICKS(1050)
|
|
|
|
#define DATA_TIME(type, data) ((data) ? (DATA_ONE_ ## type ## _TICKS) : \
|
|
(DATA_ZERO_ ## type ## _TICKS))
|
|
#define DATA_HIGH(data) DATA_TIME(HIGH, data)
|
|
#define DATA_LOW(data) DATA_TIME(LOW, data)
|
|
|
|
/*
|
|
* Number of short pulses seen before the debounce logic goes into ignoring
|
|
* the bus for DEBOUNCE_WAIT_LONG instead of DEBOUNCE_WAIT_SHORT
|
|
*/
|
|
#define DEBOUNCE_CUTOFF 3
|
|
|
|
/* The limit how short a start-bit can be to trigger debounce logic */
|
|
#define DEBOUNCE_LIMIT_TICKS APB1_TICKS(200)
|
|
/* The time we ignore the bus for the first three debounce cases */
|
|
#define DEBOUNCE_WAIT_SHORT_TICKS APB1_TICKS(100)
|
|
/* The time we ignore the bus after the three initial debounce cases */
|
|
#define DEBOUNCE_WAIT_LONG_TICKS APB1_TICKS(500)
|
|
|
|
/*
|
|
* The variance in timing we allow outside of the CEC specification for
|
|
* incoming signals. Our measurements aren't 100% accurate either, so this
|
|
* gives some robustness.
|
|
*/
|
|
#define VALID_TOLERANCE_TICKS APB1_TICKS(100)
|
|
|
|
/*
|
|
* Defines used for setting capture timers to a point where we are
|
|
* sure that if we get a timeout, something is wrong.
|
|
*/
|
|
#define CAP_START_LOW_TICKS (START_BIT_MAX_LOW_TICKS + VALID_TOLERANCE_TICKS)
|
|
#define CAP_START_HIGH_TICKS (START_BIT_MAX_DURATION_TICKS - \
|
|
START_BIT_MIN_LOW_TICKS + \
|
|
VALID_TOLERANCE_TICKS)
|
|
#define CAP_DATA_LOW_TICKS (DATA_ZERO_MAX_LOW_TICKS + VALID_TOLERANCE_TICKS)
|
|
#define CAP_DATA_HIGH_TICKS (DATA_ONE_MAX_DURATION_TICKS - \
|
|
DATA_ONE_MIN_LOW_TICKS + \
|
|
VALID_TOLERANCE_TICKS)
|
|
|
|
#define VALID_TIME(type, bit, t) \
|
|
((t) >= ((bit ## _MIN_ ## type ## _TICKS) - (VALID_TOLERANCE_TICKS)) \
|
|
&& (t) <= (bit ##_MAX_ ## type ## _TICKS) + (VALID_TOLERANCE_TICKS))
|
|
#define VALID_LOW(bit, t) VALID_TIME(LOW, bit, t)
|
|
#define VALID_HIGH(bit, low_time, high_time) \
|
|
(((low_time) + (high_time) <= \
|
|
bit ## _MAX_DURATION_TICKS + VALID_TOLERANCE_TICKS) && \
|
|
((low_time) + (high_time) >= \
|
|
bit ## _MIN_DURATION_TICKS - VALID_TOLERANCE_TICKS))
|
|
#define VALID_DATA_HIGH(data, low_time, high_time) ((data) ? \
|
|
VALID_HIGH(DATA_ONE, low_time, high_time) : \
|
|
VALID_HIGH(DATA_ZERO, low_time, high_time))
|
|
|
|
/*
|
|
* CEC state machine states. Each state typically takes action on entry and
|
|
* timeouts. INITIATIOR states are used for sending, FOLLOWER states are used
|
|
* for receiving.
|
|
*/
|
|
enum cec_state {
|
|
CEC_STATE_DISABLED = 0,
|
|
CEC_STATE_IDLE,
|
|
CEC_STATE_INITIATOR_FREE_TIME,
|
|
CEC_STATE_INITIATOR_START_LOW,
|
|
CEC_STATE_INITIATOR_START_HIGH,
|
|
CEC_STATE_INITIATOR_HEADER_INIT_LOW,
|
|
CEC_STATE_INITIATOR_HEADER_INIT_HIGH,
|
|
CEC_STATE_INITIATOR_HEADER_DEST_LOW,
|
|
CEC_STATE_INITIATOR_HEADER_DEST_HIGH,
|
|
CEC_STATE_INITIATOR_DATA_LOW,
|
|
CEC_STATE_INITIATOR_DATA_HIGH,
|
|
CEC_STATE_INITIATOR_EOM_LOW,
|
|
CEC_STATE_INITIATOR_EOM_HIGH,
|
|
CEC_STATE_INITIATOR_ACK_LOW,
|
|
CEC_STATE_INITIATOR_ACK_HIGH,
|
|
CEC_STATE_INITIATOR_ACK_VERIFY,
|
|
CEC_STATE_FOLLOWER_START_LOW,
|
|
CEC_STATE_FOLLOWER_START_HIGH,
|
|
CEC_STATE_FOLLOWER_DEBOUNCE,
|
|
CEC_STATE_FOLLOWER_HEADER_INIT_LOW,
|
|
CEC_STATE_FOLLOWER_HEADER_INIT_HIGH,
|
|
CEC_STATE_FOLLOWER_HEADER_DEST_LOW,
|
|
CEC_STATE_FOLLOWER_HEADER_DEST_HIGH,
|
|
CEC_STATE_FOLLOWER_EOM_LOW,
|
|
CEC_STATE_FOLLOWER_EOM_HIGH,
|
|
CEC_STATE_FOLLOWER_ACK_LOW,
|
|
CEC_STATE_FOLLOWER_ACK_VERIFY,
|
|
CEC_STATE_FOLLOWER_ACK_FINISH,
|
|
CEC_STATE_FOLLOWER_DATA_LOW,
|
|
CEC_STATE_FOLLOWER_DATA_HIGH,
|
|
};
|
|
|
|
/* Edge to trigger capture timer interrupt on */
|
|
enum cap_edge {
|
|
CAP_EDGE_FALLING,
|
|
CAP_EDGE_RISING
|
|
};
|
|
|
|
/* CEC message during transfer */
|
|
struct cec_msg_transfer {
|
|
/* The CEC message */
|
|
uint8_t buf[MAX_CEC_MSG_LEN];
|
|
/* Bit offset */
|
|
uint8_t bit;
|
|
/* Byte offset */
|
|
uint8_t byte;
|
|
};
|
|
|
|
/*
|
|
* Circular buffer of completed incoming CEC messages
|
|
* ready to be read out by AP
|
|
*/
|
|
struct cec_rx_cb {
|
|
/* Cicular buffer data */
|
|
uint8_t buf[CEC_CIRCBUF_SIZE];
|
|
/*
|
|
* Write offset. Updated from interrupt context when we
|
|
* have received a complete message.
|
|
*/
|
|
uint8_t write_offset;
|
|
/* Read offset. Updated when AP sends CEC read command */
|
|
uint8_t read_offset;
|
|
};
|
|
|
|
/* Receive buffer and states */
|
|
struct cec_rx {
|
|
/*
|
|
* The current incoming message being parsed. Copied to
|
|
* circular buffer upon completion
|
|
*/
|
|
struct cec_msg_transfer msgt;
|
|
/* End of Message received from source? */
|
|
uint8_t eom;
|
|
/* A follower NAK:ed a broadcast transfer */
|
|
uint8_t broadcast_nak;
|
|
/*
|
|
* Keep track of pulse low time to be able to verify
|
|
* pulse duration
|
|
*/
|
|
int low_ticks;
|
|
/* Number of too short pulses seen in a row */
|
|
int debounce_count;
|
|
};
|
|
|
|
/* Transfer buffer and states */
|
|
struct cec_tx {
|
|
/* Outgoing message */
|
|
struct cec_msg_transfer msgt;
|
|
/* Message length */
|
|
uint8_t len;
|
|
/* Number of resends attempted in current send */
|
|
uint8_t resends;
|
|
/* Acknowledge received from sink? */
|
|
uint8_t ack;
|
|
/*
|
|
* When sending multiple concurrent frames,
|
|
* the free-time is slightly higher
|
|
*/
|
|
int present_initiator;
|
|
};
|
|
|
|
/* Single state for CEC. We are INITIATOR, FOLLOWER or IDLE */
|
|
static enum cec_state cec_state;
|
|
|
|
/* Parameters and buffers for follower (receiver) state */
|
|
static struct cec_rx cec_rx;
|
|
|
|
/* Circular buffer of completed incoming */
|
|
static struct cec_rx_cb cec_rx_cb;
|
|
|
|
/* Parameters and buffer for initiator (sender) state */
|
|
static struct cec_tx cec_tx;
|
|
|
|
/*
|
|
* Time between interrupt triggered and the next timer was
|
|
* set when measuring pulse width
|
|
*/
|
|
static int cap_delay;
|
|
|
|
/* Value charged into the capture timer on last capture start */
|
|
static int cap_charge;
|
|
|
|
/*
|
|
* CEC address of ourself. We ack incoming packages on this address.
|
|
* However, the AP is responsible for writing the initiator address
|
|
* on writes. UINT32_MAX means means that the address hasn't been
|
|
* set by the AP yet.
|
|
*/
|
|
static uint8_t cec_addr = UINT8_MAX;
|
|
|
|
/* Events to send to AP */
|
|
static uint32_t cec_events;
|
|
|
|
/* APB1 frequency. Store divided by 10k to avoid some runtime divisions */
|
|
static uint32_t apb1_freq_div_10k;
|
|
|
|
/*
|
|
* Mutex for the read-offset of the circular buffer. Needed since the
|
|
* buffer is read and flushed from different contexts
|
|
*/
|
|
static struct mutex circbuf_readoffset_mutex;
|
|
|
|
static void send_mkbp_event(uint32_t event)
|
|
{
|
|
atomic_or(&cec_events, event);
|
|
mkbp_send_event(EC_MKBP_EVENT_CEC_EVENT);
|
|
}
|
|
|
|
static void tmr_cap_start(enum cap_edge edge, int timeout)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
/* Select edge to trigger capture on */
|
|
UPDATE_BIT(NPCX_TMCTRL(mdl), NPCX_TMCTRL_TAEDG,
|
|
edge == CAP_EDGE_RISING);
|
|
|
|
/*
|
|
* Set capture timeout. If we don't have a timeout, we
|
|
* turn the timeout interrupt off and only care about
|
|
* the edge change.
|
|
*/
|
|
if (timeout > 0) {
|
|
/*
|
|
* Store the time it takes from the interrupts starts to when we
|
|
* actually get here. This part of the pulse-width needs to be
|
|
* taken into account
|
|
*/
|
|
cap_delay = (0xffff - NPCX_TCNT1(mdl));
|
|
cap_charge = timeout - cap_delay;
|
|
NPCX_TCNT1(mdl) = cap_charge;
|
|
SET_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TCIEN);
|
|
} else {
|
|
CLEAR_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TCIEN);
|
|
NPCX_TCNT1(mdl) = 0;
|
|
}
|
|
|
|
/* Clear out old events */
|
|
SET_BIT(NPCX_TECLR(mdl), NPCX_TECLR_TACLR);
|
|
SET_BIT(NPCX_TECLR(mdl), NPCX_TECLR_TCCLR);
|
|
NPCX_TCRA(mdl) = 0;
|
|
/* Start the capture timer */
|
|
SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C1CSEL_FIELD, 1);
|
|
}
|
|
|
|
static void tmr_cap_stop(void)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
CLEAR_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TCIEN);
|
|
SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C1CSEL_FIELD, 0);
|
|
}
|
|
|
|
static int tmr_cap_get(void)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
return (cap_charge + cap_delay - NPCX_TCRA(mdl));
|
|
}
|
|
|
|
static void tmr_oneshot_start(int timeout)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
NPCX_TCNT1(mdl) = timeout;
|
|
SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C1CSEL_FIELD, 1);
|
|
}
|
|
|
|
static void tmr2_start(int timeout)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
NPCX_TCNT2(mdl) = timeout;
|
|
SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C2CSEL_FIELD, 1);
|
|
}
|
|
|
|
static void tmr2_stop(void)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C2CSEL_FIELD, 0);
|
|
}
|
|
|
|
static int msgt_get_bit(const struct cec_msg_transfer *msgt)
|
|
{
|
|
if (msgt->byte >= MAX_CEC_MSG_LEN)
|
|
return 0;
|
|
|
|
return msgt->buf[msgt->byte] & (0x80 >> msgt->bit);
|
|
}
|
|
|
|
static void msgt_set_bit(struct cec_msg_transfer *msgt, int val)
|
|
{
|
|
uint8_t bit_flag;
|
|
|
|
if (msgt->byte >= MAX_CEC_MSG_LEN)
|
|
return;
|
|
bit_flag = 0x80 >> msgt->bit;
|
|
msgt->buf[msgt->byte] &= ~bit_flag;
|
|
if (val)
|
|
msgt->buf[msgt->byte] |= bit_flag;
|
|
}
|
|
|
|
static void msgt_inc_bit(struct cec_msg_transfer *msgt)
|
|
{
|
|
if (++(msgt->bit) == 8) {
|
|
if (msgt->byte >= MAX_CEC_MSG_LEN)
|
|
return;
|
|
msgt->bit = 0;
|
|
msgt->byte++;
|
|
}
|
|
}
|
|
|
|
static int msgt_is_eom(const struct cec_msg_transfer *msgt, int len)
|
|
{
|
|
if (msgt->bit)
|
|
return 0;
|
|
return (msgt->byte == len);
|
|
}
|
|
|
|
static void rx_circbuf_flush(struct cec_rx_cb *cb)
|
|
{
|
|
mutex_lock(&circbuf_readoffset_mutex);
|
|
cb->read_offset = 0;
|
|
mutex_unlock(&circbuf_readoffset_mutex);
|
|
cb->write_offset = 0;
|
|
}
|
|
|
|
static int rx_circbuf_push(struct cec_rx_cb *cb, uint8_t *msg, uint8_t msg_len)
|
|
{
|
|
int i;
|
|
uint32_t offset;
|
|
|
|
if (msg_len > MAX_CEC_MSG_LEN || msg_len == 0)
|
|
return EC_ERROR_INVAL;
|
|
|
|
offset = cb->write_offset;
|
|
/* Fill in message length last, if successful. Set to zero for now */
|
|
cb->buf[offset] = 0;
|
|
offset = (offset + 1) % CEC_CIRCBUF_SIZE;
|
|
|
|
for (i = 0 ; i < msg_len; i++) {
|
|
if (offset == cb->read_offset) {
|
|
/* Buffer full */
|
|
return EC_ERROR_OVERFLOW;
|
|
}
|
|
|
|
cb->buf[offset] = msg[i];
|
|
offset = (offset + 1) % CEC_CIRCBUF_SIZE;
|
|
}
|
|
|
|
/*
|
|
* Don't commit if we caught up with read-offset
|
|
* since that would indicate an empty buffer
|
|
*/
|
|
if (offset == cb->read_offset) {
|
|
/* Buffer full */
|
|
return EC_ERROR_OVERFLOW;
|
|
}
|
|
|
|
/* Commit the push */
|
|
cb->buf[cb->write_offset] = msg_len;
|
|
cb->write_offset = offset;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int rx_circbuf_pop(struct cec_rx_cb *cb, uint8_t *msg, uint8_t *msg_len)
|
|
{
|
|
int i;
|
|
|
|
mutex_lock(&circbuf_readoffset_mutex);
|
|
if (cb->read_offset == cb->write_offset) {
|
|
/* Circular buffer empty */
|
|
mutex_unlock(&circbuf_readoffset_mutex);
|
|
*msg_len = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* The first byte in the buffer is the message length */
|
|
*msg_len = cb->buf[cb->read_offset];
|
|
if (*msg_len == 0 || *msg_len > MAX_CEC_MSG_LEN) {
|
|
mutex_unlock(&circbuf_readoffset_mutex);
|
|
*msg_len = 0;
|
|
CPRINTF("Invalid CEC msg size: %u\n", *msg_len);
|
|
return -1;
|
|
}
|
|
|
|
cb->read_offset = (cb->read_offset + 1) % CEC_CIRCBUF_SIZE;
|
|
for (i = 0; i < *msg_len; i++) {
|
|
msg[i] = cb->buf[cb->read_offset];
|
|
cb->read_offset = (cb->read_offset + 1) % CEC_CIRCBUF_SIZE;
|
|
|
|
}
|
|
|
|
mutex_unlock(&circbuf_readoffset_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void enter_state(enum cec_state new_state)
|
|
{
|
|
int gpio = -1, timeout = -1;
|
|
enum cap_edge cap_edge = -1;
|
|
uint8_t addr;
|
|
|
|
cec_state = new_state;
|
|
switch (new_state) {
|
|
case CEC_STATE_DISABLED:
|
|
gpio = 1;
|
|
memset(&cec_rx, 0, sizeof(struct cec_rx));
|
|
memset(&cec_tx, 0, sizeof(struct cec_tx));
|
|
memset(&cec_rx_cb, 0, sizeof(struct cec_rx_cb));
|
|
cap_charge = 0;
|
|
cap_delay = 0;
|
|
cec_events = 0;
|
|
break;
|
|
case CEC_STATE_IDLE:
|
|
cec_tx.msgt.bit = 0;
|
|
cec_tx.msgt.byte = 0;
|
|
cec_rx.msgt.bit = 0;
|
|
cec_rx.msgt.byte = 0;
|
|
if (cec_tx.len > 0) {
|
|
/* Execute a postponed send */
|
|
enter_state(CEC_STATE_INITIATOR_FREE_TIME);
|
|
} else {
|
|
/* Wait for incoming command */
|
|
gpio = 1;
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = 0;
|
|
}
|
|
break;
|
|
case CEC_STATE_INITIATOR_FREE_TIME:
|
|
gpio = 1;
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
if (cec_tx.resends)
|
|
timeout = FREE_TIME_RS_TICKS;
|
|
else if (cec_tx.present_initiator)
|
|
timeout = FREE_TIME_PI_TICKS;
|
|
else
|
|
timeout = FREE_TIME_NI_TICKS;
|
|
break;
|
|
case CEC_STATE_INITIATOR_START_LOW:
|
|
cec_tx.present_initiator = 1;
|
|
cec_tx.msgt.bit = 0;
|
|
cec_tx.msgt.byte = 0;
|
|
gpio = 0;
|
|
timeout = START_BIT_LOW_TICKS;
|
|
break;
|
|
case CEC_STATE_INITIATOR_START_HIGH:
|
|
gpio = 1;
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = START_BIT_HIGH_TICKS;
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_INIT_LOW:
|
|
case CEC_STATE_INITIATOR_HEADER_DEST_LOW:
|
|
case CEC_STATE_INITIATOR_DATA_LOW:
|
|
gpio = 0;
|
|
timeout = DATA_LOW(msgt_get_bit(&cec_tx.msgt));
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_INIT_HIGH:
|
|
gpio = 1;
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = DATA_HIGH(msgt_get_bit(&cec_tx.msgt));
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_DEST_HIGH:
|
|
case CEC_STATE_INITIATOR_DATA_HIGH:
|
|
gpio = 1;
|
|
timeout = DATA_HIGH(msgt_get_bit(&cec_tx.msgt));
|
|
break;
|
|
case CEC_STATE_INITIATOR_EOM_LOW:
|
|
gpio = 0;
|
|
timeout = DATA_LOW(msgt_is_eom(&cec_tx.msgt, cec_tx.len));
|
|
break;
|
|
case CEC_STATE_INITIATOR_EOM_HIGH:
|
|
gpio = 1;
|
|
timeout = DATA_HIGH(msgt_is_eom(&cec_tx.msgt, cec_tx.len));
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_LOW:
|
|
gpio = 0;
|
|
timeout = DATA_LOW(1);
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_HIGH:
|
|
gpio = 1;
|
|
/* Aim for the middle of the safe sample time */
|
|
timeout = (DATA_ONE_LOW_TICKS + DATA_ZERO_LOW_TICKS)/2 -
|
|
DATA_ONE_LOW_TICKS;
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_VERIFY:
|
|
cec_tx.ack = !gpio_get_level(CEC_GPIO_OUT);
|
|
if ((cec_tx.msgt.buf[0] & 0x0f) == CEC_BROADCAST_ADDR) {
|
|
/*
|
|
* We are sending a broadcast. Any follower can
|
|
* can NAK a broadcast message the same way they
|
|
* would ACK a direct message
|
|
*/
|
|
cec_tx.ack = !cec_tx.ack;
|
|
}
|
|
/*
|
|
* We are at the safe sample time. Wait
|
|
* until the end of this bit
|
|
*/
|
|
timeout = NOMINAL_BIT_TICKS - NOMINAL_SAMPLE_TIME_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_START_LOW:
|
|
cec_tx.present_initiator = 0;
|
|
cap_edge = CAP_EDGE_RISING;
|
|
timeout = CAP_START_LOW_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_START_HIGH:
|
|
cec_rx.debounce_count = 0;
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = CAP_START_HIGH_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_DEBOUNCE:
|
|
if (cec_rx.debounce_count >= DEBOUNCE_CUTOFF) {
|
|
timeout = DEBOUNCE_WAIT_LONG_TICKS;
|
|
} else {
|
|
timeout = DEBOUNCE_WAIT_SHORT_TICKS;
|
|
cec_rx.debounce_count++;
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_LOW:
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_LOW:
|
|
case CEC_STATE_FOLLOWER_EOM_LOW:
|
|
cap_edge = CAP_EDGE_RISING;
|
|
timeout = CAP_DATA_LOW_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_HIGH:
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_HIGH:
|
|
case CEC_STATE_FOLLOWER_EOM_HIGH:
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = CAP_DATA_HIGH_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_LOW:
|
|
addr = cec_rx.msgt.buf[0] & 0x0f;
|
|
if (addr == cec_addr) {
|
|
/* Destination is our address */
|
|
gpio = 0;
|
|
timeout = NOMINAL_SAMPLE_TIME_TICKS;
|
|
} else if (addr == CEC_BROADCAST_ADDR) {
|
|
/* Don't ack broadcast or packets which destination
|
|
* are us, but continue reading
|
|
*/
|
|
timeout = NOMINAL_SAMPLE_TIME_TICKS;
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_VERIFY:
|
|
/*
|
|
* We are at safe sample time. A broadcast frame is considered
|
|
* lost if any follower pulls the line low
|
|
*/
|
|
if ((cec_rx.msgt.buf[0] & 0x0f) == CEC_BROADCAST_ADDR)
|
|
cec_rx.broadcast_nak = !gpio_get_level(CEC_GPIO_OUT);
|
|
else
|
|
cec_rx.broadcast_nak = 0;
|
|
|
|
/*
|
|
* We release the ACK at the end of data zero low
|
|
* period (ACK is technically a zero).
|
|
*/
|
|
timeout = DATA_ZERO_LOW_TICKS - NOMINAL_SAMPLE_TIME_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_FINISH:
|
|
gpio = 1;
|
|
if (cec_rx.eom || cec_rx.msgt.byte >= MAX_CEC_MSG_LEN) {
|
|
addr = cec_rx.msgt.buf[0] & 0x0f;
|
|
if (addr == cec_addr || addr == CEC_BROADCAST_ADDR) {
|
|
task_set_event(TASK_ID_CEC,
|
|
TASK_EVENT_RECEIVED_DATA, 0);
|
|
}
|
|
timeout = DATA_ZERO_HIGH_TICKS;
|
|
} else {
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = CAP_DATA_HIGH_TICKS;
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_DATA_LOW:
|
|
cap_edge = CAP_EDGE_RISING;
|
|
timeout = CAP_DATA_LOW_TICKS;
|
|
break;
|
|
case CEC_STATE_FOLLOWER_DATA_HIGH:
|
|
cap_edge = CAP_EDGE_FALLING;
|
|
timeout = CAP_DATA_HIGH_TICKS;
|
|
break;
|
|
/* No default case, since all states must be handled explicitly */
|
|
}
|
|
|
|
if (gpio >= 0)
|
|
gpio_set_level(CEC_GPIO_OUT, gpio);
|
|
if (timeout >= 0) {
|
|
if (cap_edge >= 0)
|
|
tmr_cap_start(cap_edge, timeout);
|
|
else
|
|
tmr_oneshot_start(timeout);
|
|
}
|
|
}
|
|
|
|
static void cec_event_timeout(void)
|
|
{
|
|
switch (cec_state) {
|
|
case CEC_STATE_DISABLED:
|
|
case CEC_STATE_IDLE:
|
|
break;
|
|
case CEC_STATE_INITIATOR_FREE_TIME:
|
|
enter_state(CEC_STATE_INITIATOR_START_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_START_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_START_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_START_HIGH:
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_INIT_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_INIT_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_INIT_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_INIT_HIGH:
|
|
msgt_inc_bit(&cec_tx.msgt);
|
|
if (cec_tx.msgt.bit == 4)
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_DEST_LOW);
|
|
else
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_INIT_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_DEST_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_DEST_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_HEADER_DEST_HIGH:
|
|
msgt_inc_bit(&cec_tx.msgt);
|
|
if (cec_tx.msgt.byte == 1)
|
|
enter_state(CEC_STATE_INITIATOR_EOM_LOW);
|
|
else
|
|
enter_state(CEC_STATE_INITIATOR_HEADER_DEST_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_EOM_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_EOM_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_EOM_HIGH:
|
|
enter_state(CEC_STATE_INITIATOR_ACK_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_ACK_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_HIGH:
|
|
enter_state(CEC_STATE_INITIATOR_ACK_VERIFY);
|
|
break;
|
|
case CEC_STATE_INITIATOR_ACK_VERIFY:
|
|
if (cec_tx.ack) {
|
|
if (!msgt_is_eom(&cec_tx.msgt, cec_tx.len)) {
|
|
/* More data in this frame */
|
|
enter_state(CEC_STATE_INITIATOR_DATA_LOW);
|
|
} else {
|
|
/* Transfer completed successfully */
|
|
cec_tx.len = 0;
|
|
cec_tx.resends = 0;
|
|
enter_state(CEC_STATE_IDLE);
|
|
send_mkbp_event(EC_MKBP_CEC_SEND_OK);
|
|
}
|
|
} else {
|
|
if (cec_tx.resends < CEC_MAX_RESENDS) {
|
|
/* Resend */
|
|
cec_tx.resends++;
|
|
enter_state(CEC_STATE_INITIATOR_FREE_TIME);
|
|
} else {
|
|
/* Transfer failed */
|
|
cec_tx.len = 0;
|
|
cec_tx.resends = 0;
|
|
enter_state(CEC_STATE_IDLE);
|
|
send_mkbp_event(EC_MKBP_CEC_SEND_FAILED);
|
|
}
|
|
}
|
|
break;
|
|
case CEC_STATE_INITIATOR_DATA_LOW:
|
|
enter_state(CEC_STATE_INITIATOR_DATA_HIGH);
|
|
break;
|
|
case CEC_STATE_INITIATOR_DATA_HIGH:
|
|
msgt_inc_bit(&cec_tx.msgt);
|
|
if (cec_tx.msgt.bit == 0)
|
|
enter_state(CEC_STATE_INITIATOR_EOM_LOW);
|
|
else
|
|
enter_state(CEC_STATE_INITIATOR_DATA_LOW);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_LOW:
|
|
enter_state(CEC_STATE_FOLLOWER_ACK_VERIFY);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_VERIFY:
|
|
if (cec_rx.broadcast_nak)
|
|
enter_state(CEC_STATE_IDLE);
|
|
else
|
|
enter_state(CEC_STATE_FOLLOWER_ACK_FINISH);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_START_LOW:
|
|
case CEC_STATE_FOLLOWER_START_HIGH:
|
|
case CEC_STATE_FOLLOWER_DEBOUNCE:
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_LOW:
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_HIGH:
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_LOW:
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_HIGH:
|
|
case CEC_STATE_FOLLOWER_EOM_LOW:
|
|
case CEC_STATE_FOLLOWER_EOM_HIGH:
|
|
case CEC_STATE_FOLLOWER_ACK_FINISH:
|
|
case CEC_STATE_FOLLOWER_DATA_LOW:
|
|
case CEC_STATE_FOLLOWER_DATA_HIGH:
|
|
enter_state(CEC_STATE_IDLE);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
static void cec_event_cap(void)
|
|
{
|
|
int t;
|
|
int data;
|
|
|
|
switch (cec_state) {
|
|
case CEC_STATE_IDLE:
|
|
/* A falling edge during idle, likely a start bit */
|
|
enter_state(CEC_STATE_FOLLOWER_START_LOW);
|
|
break;
|
|
case CEC_STATE_INITIATOR_FREE_TIME:
|
|
case CEC_STATE_INITIATOR_START_HIGH:
|
|
case CEC_STATE_INITIATOR_HEADER_INIT_HIGH:
|
|
/*
|
|
* A falling edge during free-time, postpone
|
|
* this send and listen
|
|
*/
|
|
cec_tx.msgt.bit = 0;
|
|
cec_tx.msgt.byte = 0;
|
|
enter_state(CEC_STATE_FOLLOWER_START_LOW);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_START_LOW:
|
|
/* Rising edge of start bit, validate low time */
|
|
t = tmr_cap_get();
|
|
if (VALID_LOW(START_BIT, t)) {
|
|
cec_rx.low_ticks = t;
|
|
enter_state(CEC_STATE_FOLLOWER_START_HIGH);
|
|
} else if (t < DEBOUNCE_LIMIT_TICKS) {
|
|
/* Wait a bit if start-pulses are really short */
|
|
enter_state(CEC_STATE_FOLLOWER_DEBOUNCE);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_START_HIGH:
|
|
if (VALID_HIGH(START_BIT, cec_rx.low_ticks, tmr_cap_get()))
|
|
enter_state(CEC_STATE_FOLLOWER_HEADER_INIT_LOW);
|
|
else
|
|
enter_state(CEC_STATE_IDLE);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_LOW:
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_LOW:
|
|
case CEC_STATE_FOLLOWER_DATA_LOW:
|
|
t = tmr_cap_get();
|
|
if (VALID_LOW(DATA_ZERO, t)) {
|
|
cec_rx.low_ticks = t;
|
|
msgt_set_bit(&cec_rx.msgt, 0);
|
|
enter_state(cec_state + 1);
|
|
} else if (VALID_LOW(DATA_ONE, t)) {
|
|
cec_rx.low_ticks = t;
|
|
msgt_set_bit(&cec_rx.msgt, 1);
|
|
enter_state(cec_state + 1);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_HEADER_INIT_HIGH:
|
|
t = tmr_cap_get();
|
|
data = msgt_get_bit(&cec_rx.msgt);
|
|
if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) {
|
|
msgt_inc_bit(&cec_rx.msgt);
|
|
if (cec_rx.msgt.bit == 4)
|
|
enter_state(CEC_STATE_FOLLOWER_HEADER_DEST_LOW);
|
|
else
|
|
enter_state(CEC_STATE_FOLLOWER_HEADER_INIT_LOW);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_HEADER_DEST_HIGH:
|
|
t = tmr_cap_get();
|
|
data = msgt_get_bit(&cec_rx.msgt);
|
|
if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) {
|
|
msgt_inc_bit(&cec_rx.msgt);
|
|
if (cec_rx.msgt.bit == 0)
|
|
enter_state(CEC_STATE_FOLLOWER_EOM_LOW);
|
|
else
|
|
enter_state(CEC_STATE_FOLLOWER_HEADER_DEST_LOW);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_EOM_LOW:
|
|
t = tmr_cap_get();
|
|
if (VALID_LOW(DATA_ZERO, t)) {
|
|
cec_rx.low_ticks = t;
|
|
cec_rx.eom = 0;
|
|
enter_state(CEC_STATE_FOLLOWER_EOM_HIGH);
|
|
} else if (VALID_LOW(DATA_ONE, t)) {
|
|
cec_rx.low_ticks = t;
|
|
cec_rx.eom = 1;
|
|
enter_state(CEC_STATE_FOLLOWER_EOM_HIGH);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
case CEC_STATE_FOLLOWER_EOM_HIGH:
|
|
t = tmr_cap_get();
|
|
data = cec_rx.eom;
|
|
if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t))
|
|
enter_state(CEC_STATE_FOLLOWER_ACK_LOW);
|
|
else
|
|
enter_state(CEC_STATE_IDLE);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_LOW:
|
|
enter_state(CEC_STATE_FOLLOWER_ACK_FINISH);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_ACK_FINISH:
|
|
enter_state(CEC_STATE_FOLLOWER_DATA_LOW);
|
|
break;
|
|
case CEC_STATE_FOLLOWER_DATA_HIGH:
|
|
t = tmr_cap_get();
|
|
data = msgt_get_bit(&cec_rx.msgt);
|
|
if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) {
|
|
msgt_inc_bit(&cec_rx.msgt);
|
|
if (cec_rx.msgt.bit == 0)
|
|
enter_state(CEC_STATE_FOLLOWER_EOM_LOW);
|
|
else
|
|
enter_state(CEC_STATE_FOLLOWER_DATA_LOW);
|
|
} else {
|
|
enter_state(CEC_STATE_IDLE);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cec_event_tx(void)
|
|
{
|
|
/*
|
|
* If we have an ongoing receive, this transfer
|
|
* will start when transitioning to IDLE
|
|
*/
|
|
if (cec_state == CEC_STATE_IDLE)
|
|
enter_state(CEC_STATE_INITIATOR_FREE_TIME);
|
|
}
|
|
|
|
static void cec_isr(void)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
uint8_t events;
|
|
|
|
/* Retrieve events NPCX_TECTRL_TAXND */
|
|
events = GET_FIELD(NPCX_TECTRL(mdl), FIELD(0, 4));
|
|
|
|
if (events & (1 << NPCX_TECTRL_TAPND)) {
|
|
/* Capture event */
|
|
cec_event_cap();
|
|
} else {
|
|
/*
|
|
* Capture timeout
|
|
* We only care about this if the capture event is not
|
|
* happening, since we will get both events in the
|
|
* edge-trigger case
|
|
*/
|
|
if (events & (1 << NPCX_TECTRL_TCPND))
|
|
cec_event_timeout();
|
|
}
|
|
/* Oneshot timer, a transfer has been initiated from AP */
|
|
if (events & (1 << NPCX_TECTRL_TDPND)) {
|
|
tmr2_stop();
|
|
cec_event_tx();
|
|
}
|
|
|
|
/* Clear handled events */
|
|
SET_FIELD(NPCX_TECLR(mdl), FIELD(0, 4), events);
|
|
}
|
|
DECLARE_IRQ(NPCX_IRQ_MFT_1, cec_isr, 4);
|
|
|
|
static int cec_send(const uint8_t *msg, uint8_t len)
|
|
{
|
|
int i;
|
|
|
|
if (cec_tx.len != 0)
|
|
return -1;
|
|
|
|
cec_tx.len = len;
|
|
|
|
CPRINTS("Send CEC:");
|
|
for (i = 0; i < len && i < MAX_CEC_MSG_LEN; i++)
|
|
CPRINTS(" 0x%02x", msg[i]);
|
|
|
|
memcpy(cec_tx.msgt.buf, msg, len);
|
|
|
|
/* Elevate to interrupt context */
|
|
tmr2_start(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hc_cec_write(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_cec_write *params = args->params;
|
|
|
|
if (cec_state == CEC_STATE_DISABLED)
|
|
return EC_RES_UNAVAILABLE;
|
|
|
|
if (args->params_size == 0 || args->params_size > MAX_CEC_MSG_LEN)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
if (cec_send(params->msg, args->params_size) != 0)
|
|
return EC_RES_BUSY;
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_CEC_WRITE_MSG, hc_cec_write, EC_VER_MASK(0));
|
|
|
|
static int cec_set_enable(uint8_t enable)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
if (enable != 0 && enable != 1)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
/* Enabling when already enabled? */
|
|
if (enable && cec_state != CEC_STATE_DISABLED)
|
|
return EC_RES_SUCCESS;
|
|
|
|
/* Disabling when already disabled? */
|
|
if (!enable && cec_state == CEC_STATE_DISABLED)
|
|
return EC_RES_SUCCESS;
|
|
|
|
if (enable) {
|
|
/* Configure GPIO40/TA1 as capture timer input (TA1) */
|
|
CLEAR_BIT(NPCX_DEVALT(0xC), NPCX_DEVALTC_TA1_SL2);
|
|
SET_BIT(NPCX_DEVALT(3), NPCX_DEVALT3_TA1_SL1);
|
|
|
|
enter_state(CEC_STATE_IDLE);
|
|
|
|
/*
|
|
* Capture falling edge of first start
|
|
* bit to get things going
|
|
*/
|
|
tmr_cap_start(CAP_EDGE_FALLING, 0);
|
|
|
|
/* Enable timer interrupts */
|
|
SET_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TAIEN);
|
|
SET_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TDIEN);
|
|
|
|
/* Enable multifunction timer interrupt */
|
|
task_enable_irq(NPCX_IRQ_MFT_1);
|
|
|
|
CPRINTF("CEC enabled\n");
|
|
} else {
|
|
/* Disable timer interrupts */
|
|
CLEAR_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TAIEN);
|
|
CLEAR_BIT(NPCX_TIEN(mdl), NPCX_TIEN_TDIEN);
|
|
|
|
tmr2_stop();
|
|
tmr_cap_stop();
|
|
|
|
task_disable_irq(NPCX_IRQ_MFT_1);
|
|
|
|
/* Configure GPIO40/TA1 back to GPIO */
|
|
CLEAR_BIT(NPCX_DEVALT(3), NPCX_DEVALT3_TA1_SL1);
|
|
SET_BIT(NPCX_DEVALT(0xC), NPCX_DEVALTC_TA1_SL2);
|
|
|
|
enter_state(CEC_STATE_DISABLED);
|
|
|
|
CPRINTF("CEC disabled\n");
|
|
}
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
|
|
static int cec_set_logical_addr(uint8_t logical_addr)
|
|
{
|
|
if (logical_addr >= CEC_BROADCAST_ADDR &&
|
|
logical_addr != CEC_UNREGISTERED_ADDR)
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
cec_addr = logical_addr;
|
|
CPRINTF("CEC address set to: %u\n", cec_addr);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
|
|
static int hc_cec_set(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_cec_set *params = args->params;
|
|
|
|
switch (params->cmd) {
|
|
case CEC_CMD_ENABLE:
|
|
return cec_set_enable(params->val);
|
|
case CEC_CMD_LOGICAL_ADDRESS:
|
|
return cec_set_logical_addr(params->val);
|
|
}
|
|
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_CEC_SET, hc_cec_set, EC_VER_MASK(0));
|
|
|
|
|
|
static int hc_cec_get(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_cec_get *response = args->response;
|
|
const struct ec_params_cec_get *params = args->params;
|
|
|
|
switch (params->cmd) {
|
|
case CEC_CMD_ENABLE:
|
|
response->val = cec_state == CEC_STATE_DISABLED ? 0 : 1;
|
|
break;
|
|
case CEC_CMD_LOGICAL_ADDRESS:
|
|
response->val = cec_addr;
|
|
break;
|
|
default:
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
args->response_size = sizeof(*response);
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_CEC_GET, hc_cec_get, EC_VER_MASK(0));
|
|
|
|
static int cec_get_next_event(uint8_t *out)
|
|
{
|
|
uint32_t event_out = atomic_read_clear(&cec_events);
|
|
|
|
memcpy(out, &event_out, sizeof(event_out));
|
|
|
|
return sizeof(event_out);
|
|
}
|
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_CEC_EVENT, cec_get_next_event);
|
|
|
|
static int cec_get_next_msg(uint8_t *out)
|
|
{
|
|
int rv;
|
|
uint8_t msg_len, msg[MAX_CEC_MSG_LEN];
|
|
|
|
rv = rx_circbuf_pop(&cec_rx_cb, msg, &msg_len);
|
|
if (rv != 0)
|
|
return EC_RES_UNAVAILABLE;
|
|
|
|
memcpy(out, msg, msg_len);
|
|
|
|
return msg_len;
|
|
}
|
|
DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_CEC_MESSAGE, cec_get_next_msg);
|
|
|
|
|
|
static void cec_init(void)
|
|
{
|
|
int mdl = NPCX_MFT_MODULE_1;
|
|
|
|
/* APB1 is the clock we base the timers on */
|
|
apb1_freq_div_10k = clock_get_apb1_freq()/10000;
|
|
|
|
/* Ensure Multi-Function timer is powered up. */
|
|
CLEAR_BIT(NPCX_PWDWN_CTL(mdl), NPCX_PWDWN_CTL1_MFT1_PD);
|
|
|
|
/* Mode 2 - Dual-input capture */
|
|
SET_FIELD(NPCX_TMCTRL(mdl), NPCX_TMCTRL_MDSEL_FIELD, NPCX_MFT_MDSEL_2);
|
|
|
|
/* Enable capture TCNT1 into TCRA and preset TCNT1. */
|
|
SET_BIT(NPCX_TMCTRL(mdl), NPCX_TMCTRL_TAEN);
|
|
|
|
CPRINTS("CEC initialized");
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, cec_init, HOOK_PRIO_LAST);
|
|
|
|
void cec_task(void *unused)
|
|
{
|
|
int rv;
|
|
uint32_t events;
|
|
|
|
CPRINTF("CEC task starting\n");
|
|
|
|
while (1) {
|
|
events = task_wait_event(-1);
|
|
if (events & TASK_EVENT_RECEIVED_DATA) {
|
|
rv = rx_circbuf_push(&cec_rx_cb, cec_rx.msgt.buf,
|
|
cec_rx.msgt.byte);
|
|
if (rv == EC_ERROR_OVERFLOW) {
|
|
/* Buffer full, prefer the most recent msg */
|
|
rx_circbuf_flush(&cec_rx_cb);
|
|
rv = rx_circbuf_push(&cec_rx_cb,
|
|
cec_rx.msgt.buf,
|
|
cec_rx.msgt.byte);
|
|
}
|
|
if (rv == EC_SUCCESS)
|
|
mkbp_send_event(EC_MKBP_EVENT_CEC_MESSAGE);
|
|
}
|
|
}
|
|
}
|