/* 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 "cec.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 /* * 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 }; /* Receive buffer and states */ struct cec_rx { /* * The current incoming message being parsed. Copied to * receive queue upon completion */ struct cec_msg_transfer transfer; /* 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 transfer; /* 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; /* Queue of completed incoming CEC messages */ static struct cec_rx_queue cec_rx_queue; /* 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; 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); } 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_queue, 0, sizeof(struct cec_rx_queue)); cap_charge = 0; cap_delay = 0; cec_events = 0; break; case CEC_STATE_IDLE: cec_tx.transfer.bit = 0; cec_tx.transfer.byte = 0; cec_rx.transfer.bit = 0; cec_rx.transfer.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.transfer.bit = 0; cec_tx.transfer.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(cec_transfer_get_bit(&cec_tx.transfer)); break; case CEC_STATE_INITIATOR_HEADER_INIT_HIGH: gpio = 1; cap_edge = CAP_EDGE_FALLING; timeout = DATA_HIGH(cec_transfer_get_bit(&cec_tx.transfer)); break; case CEC_STATE_INITIATOR_HEADER_DEST_HIGH: case CEC_STATE_INITIATOR_DATA_HIGH: gpio = 1; timeout = DATA_HIGH(cec_transfer_get_bit(&cec_tx.transfer)); break; case CEC_STATE_INITIATOR_EOM_LOW: gpio = 0; timeout = DATA_LOW(cec_transfer_is_eom(&cec_tx.transfer, cec_tx.len)); break; case CEC_STATE_INITIATOR_EOM_HIGH: gpio = 1; timeout = DATA_HIGH(cec_transfer_is_eom(&cec_tx.transfer, 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.transfer.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.transfer.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.transfer.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.transfer.byte >= MAX_CEC_MSG_LEN) { addr = cec_rx.transfer.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: cec_transfer_inc_bit(&cec_tx.transfer); if (cec_tx.transfer.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: cec_transfer_inc_bit(&cec_tx.transfer); if (cec_tx.transfer.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 (!cec_transfer_is_eom(&cec_tx.transfer, 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: cec_transfer_inc_bit(&cec_tx.transfer); if (cec_tx.transfer.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.transfer.bit = 0; cec_tx.transfer.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; cec_transfer_set_bit(&cec_rx.transfer, 0); enter_state(cec_state + 1); } else if (VALID_LOW(DATA_ONE, t)) { cec_rx.low_ticks = t; cec_transfer_set_bit(&cec_rx.transfer, 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 = cec_transfer_get_bit(&cec_rx.transfer); if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) { cec_transfer_inc_bit(&cec_rx.transfer); if (cec_rx.transfer.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 = cec_transfer_get_bit(&cec_rx.transfer); if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) { cec_transfer_inc_bit(&cec_rx.transfer); if (cec_rx.transfer.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 = cec_transfer_get_bit(&cec_rx.transfer); if (VALID_DATA_HIGH(data, cec_rx.low_ticks, t)) { cec_transfer_inc_bit(&cec_rx.transfer); if (cec_rx.transfer.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); } 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.transfer.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 = cec_rx_queue_pop(&cec_rx_queue, 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 = cec_rx_queue_push(&cec_rx_queue, cec_rx.transfer.buf, cec_rx.transfer.byte); if (rv == EC_ERROR_OVERFLOW) { /* Queue full, prefer the most recent msg */ cec_rx_queue_flush(&cec_rx_queue); rv = cec_rx_queue_push(&cec_rx_queue, cec_rx.transfer.buf, cec_rx.transfer.byte); } if (rv == EC_SUCCESS) mkbp_send_event(EC_MKBP_EVENT_CEC_MESSAGE); } } }