blob: 3d5f7af1e94be3b8b1bc318b99ae2d40d680631c [file] [log] [blame]
/* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "cec.h"
#include "cec_bitbang_chip.h"
#include "console.h"
#include "driver/cec/bitbang.h"
#include "gpio.h"
#include "hwtimer_chip.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#define CPRINTF(format, args...) cprintf(CC_CEC, format, ##args)
#define CPRINTS(format, args...) cprints(CC_CEC, format, ##args)
#ifdef CONFIG_CEC_DEBUG
#define DEBUG_CPRINTF(format, args...) cprintf(CC_CEC, format, ##args)
#define DEBUG_CPRINTS(format, args...) cprints(CC_CEC, format, ##args)
#else
#define DEBUG_CPRINTF(...)
#define DEBUG_CPRINTS(...)
#endif
/* Timestamp when the most recent interrupt occurred */
static timestamp_t interrupt_time;
/* Timestamp when the second most recent interrupt occurred */
static timestamp_t prev_interrupt_time;
/* Flag set when a transfer is initiated from the AP */
static bool transfer_initiated;
/* The capture edge we're waiting for */
static enum cec_cap_edge expected_cap_edge;
static int port_from_timer(enum ext_timer_sel ext_timer)
{
int port;
const struct bitbang_cec_config *drv_config;
for (port = 0; port < CEC_PORT_COUNT; port++) {
if (cec_config[port].drv == &bitbang_cec_drv) {
drv_config = cec_config[port].drv_config;
if (drv_config->timer == ext_timer)
return port;
}
}
/*
* If we don't find a match, return 0. The only way for this to happen
* is a configuration error, e.g. an incorrect timer is specified in
* board.c, and we assume static configuration is correct to improve
* performance.
*/
return 0;
}
static int port_from_gpio_in(enum gpio_signal signal)
{
int port;
const struct bitbang_cec_config *drv_config;
for (port = 0; port < CEC_PORT_COUNT; port++) {
if (cec_config[port].drv == &bitbang_cec_drv) {
drv_config = cec_config[port].drv_config;
if (drv_config->gpio_in == signal)
return port;
}
}
/*
* If we don't find a match, return 0. The only way for this to happen
* is a configuration error, e.g. an incorrect pin is mapped to
* cec_gpio_interrupt in gpio.inc, and we assume static configuration
* is correct to improve performance.
*/
return 0;
}
/*
* ITE doesn't have a capture timer, so we use a countdown timer for timeout
* events combined with a GPIO interrupt for capture events.
*/
void cec_tmr_cap_start(int port, enum cec_cap_edge edge, int timeout)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
expected_cap_edge = edge;
if (timeout > 0) {
/*
* Take into account the delay from when the interrupt occurs to
* when we actually get here. Since the timing is done in
* software, there is an additional unknown delay from when the
* interrupt occurs to when the ISR starts. Empirically, this
* seems to be about 100 us, so account for this too.
*/
int delay = CEC_US_TO_TICKS(get_time().val -
interrupt_time.val + 100);
int timer_count = timeout - delay;
/*
* Handle the case where the delay is greater than the timeout.
* This should never actually happen for typical delay and
* timeout values.
*/
if (timer_count < 0) {
timer_count = 0;
CPRINTS("CEC%d warning: timer_count < 0", port);
}
/* Start the timer and enable the timer interrupt */
ext_timer_ms(drv_config->timer, CEC_CLOCK_SOURCE, 1, 1,
timer_count, 0, 1);
} else {
ext_timer_stop(drv_config->timer, 1);
}
}
void cec_tmr_cap_stop(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
gpio_disable_interrupt(drv_config->gpio_in);
ext_timer_stop(drv_config->timer, 1);
}
int cec_tmr_cap_get(int port)
{
return CEC_US_TO_TICKS(interrupt_time.val - prev_interrupt_time.val);
}
/*
* In most states it83xx keeps gpio interrupts enabled to improve timing (see
* https://crrev.com/c/4899696). But for the debounce logic to work, gpio
* interrupts must be disabled, so we disable them when entering the debounce
* state and re-enable them when leaving the state.
*/
void cec_debounce_enable(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
gpio_disable_interrupt(drv_config->gpio_in);
}
void cec_debounce_disable(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
gpio_enable_interrupt(drv_config->gpio_in);
}
__override void cec_update_interrupt_time(int port)
{
prev_interrupt_time = interrupt_time;
interrupt_time = get_time();
}
void cec_ext_timer_interrupt(enum ext_timer_sel ext_timer)
{
int port = port_from_timer(ext_timer);
if (transfer_initiated) {
transfer_initiated = false;
cec_event_tx(port);
} else {
cec_update_interrupt_time(port);
cec_event_timeout(port);
}
}
void cec_gpio_interrupt(enum gpio_signal signal)
{
int port = port_from_gpio_in(signal);
int level;
cec_update_interrupt_time(port);
level = gpio_get_level(signal);
if (!((expected_cap_edge == CEC_CAP_EDGE_FALLING && level == 0) ||
(expected_cap_edge == CEC_CAP_EDGE_RISING && level == 1)))
return;
cec_event_cap(port);
}
void cec_trigger_send(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
/* Elevate to interrupt context */
transfer_initiated = true;
task_trigger_irq(et_ctrl_regs[drv_config->timer].irq);
}
void cec_enable_timer(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
/*
* Enable gpio interrupts. Timer interrupts will be enabled as needed by
* cec_tmr_cap_start().
*/
gpio_enable_interrupt(drv_config->gpio_in);
}
void cec_disable_timer(int port)
{
cec_tmr_cap_stop(port);
interrupt_time.val = 0;
prev_interrupt_time.val = 0;
}
void cec_init_timer(int port)
{
const struct bitbang_cec_config *drv_config =
cec_config[port].drv_config;
ext_timer_ms(drv_config->timer, CEC_CLOCK_SOURCE, 0, 0, 0, 1, 0);
}