esp-idf/components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c

151 wiersze
6.8 KiB
C

/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_clk_tree.h"
#include "mipi_dsi_priv.h"
static const char *TAG = "lcd.dsi.bus";
#define MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ 10
// TxClkEsc frequency must be configured between 2 and 20 MHz
#define MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ 10
esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(bus_config->num_data_lanes <= MIPI_DSI_LL_MAX_DATA_LANES,
ESP_ERR_INVALID_ARG, TAG, "invalid number of data lanes %d", bus_config->num_data_lanes);
ESP_RETURN_ON_FALSE(bus_config->lane_bit_rate_mbps >= MIPI_DSI_LL_MIN_PHY_MBPS &&
bus_config->lane_bit_rate_mbps <= MIPI_DSI_LL_MAX_PHY_MBPS, ESP_ERR_INVALID_ARG, TAG,
"invalid lane bit rate %"PRIu32, bus_config->lane_bit_rate_mbps);
// we don't use an bus allocator here, because different DSI bus uses different PHY.
// And each PHY has its own associated PINs, which is not changeable.
// So user HAS TO specify the bus ID by themselves, according to their PCB design.
int bus_id = bus_config->bus_id;
ESP_RETURN_ON_FALSE(bus_id >= 0 && bus_id < MIPI_DSI_LL_NUM_BUS, ESP_ERR_INVALID_ARG, TAG, "invalid bus ID %d", bus_id);
esp_lcd_dsi_bus_t *dsi_bus = heap_caps_calloc(1, sizeof(esp_lcd_dsi_bus_t), DSI_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(dsi_bus, ESP_ERR_NO_MEM, TAG, "no memory for DSI bus");
dsi_bus->bus_id = bus_id;
// Enable the APB clock for accessing the DSI host and bridge registers
DSI_RCC_ATOMIC() {
mipi_dsi_ll_enable_bus_clock(bus_id, true);
mipi_dsi_ll_reset_register(bus_id);
}
// if the clock source is not assigned, fallback to the default clock source
mipi_dsi_phy_clock_source_t phy_clk_src = bus_config->phy_clk_src;
if (phy_clk_src == 0) {
phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT;
}
// enable the clock source for DSI PHY
DSI_CLOCK_SRC_ATOMIC() {
// set clock source for DSI PHY
mipi_dsi_ll_set_phy_clock_source(bus_id, phy_clk_src);
// the configuration clock is used for all modes except the shutdown mode
mipi_dsi_ll_enable_phy_config_clock(bus_id, true);
// enable the clock for generating the serial clock
mipi_dsi_ll_enable_phy_reference_clock(bus_id, true);
}
#if CONFIG_PM_ENABLE
// When MIPI DSI is working, we don't expect the clock source would be turned off
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
ret = esp_pm_lock_create(pm_lock_type, 0, "dsi_phy", &dsi_bus->pm_lock);
ESP_GOTO_ON_ERROR(ret, err, TAG, "create PM lock failed");
// before we configure the PLL, we want the clock source to be stable
esp_pm_lock_acquire(dsi_bus->pm_lock);
#endif
// if the number of data lanes is not assigned, fallback to the maximum number of data lanes
int num_data_lanes = bus_config->num_data_lanes;
if (num_data_lanes == 0) {
num_data_lanes = MIPI_DSI_LL_MAX_DATA_LANES;
}
// initialize HAL context
mipi_dsi_hal_config_t hal_config = {
.bus_id = bus_id,
.lane_bit_rate_mbps = bus_config->lane_bit_rate_mbps,
.num_data_lanes = num_data_lanes,
};
mipi_dsi_hal_init(&dsi_bus->hal, &hal_config);
mipi_dsi_hal_context_t *hal = &dsi_bus->hal;
// get the frequency of the PHY clock source
uint32_t phy_clk_src_freq_hz = 0;
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(phy_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
&phy_clk_src_freq_hz), err, TAG, "get phy clock source frequency failed");
// configure the PHY PLL
mipi_dsi_hal_configure_phy_pll(hal, phy_clk_src_freq_hz, bus_config->lane_bit_rate_mbps);
// wait for PHY initialization done
while (!mipi_dsi_phy_ll_is_pll_locked(hal->host)) {
vTaskDelay(pdMS_TO_TICKS(1));
}
while (!mipi_dsi_phy_ll_are_lanes_stopped(hal->host, num_data_lanes)) {
vTaskDelay(pdMS_TO_TICKS(1));
}
// initialize the DSI operation mode: command mode
mipi_dsi_host_ll_enable_video_mode(hal->host, false);
// place the clock lane in low power mode, we will switch to high speed mode later when DPI stream is ready
mipi_dsi_host_ll_set_clock_lane_state(hal->host, MIPI_DSI_LL_CLOCK_LANE_STATE_LP);
// Set the time that is required by the clock and data lanes to go from high-speed to low-power and from low-power to high-speed
mipi_dsi_phy_ll_set_switch_time(hal->host, 50, 104, 46, 128);
// enable CRC reception and ECC reception, error correction, and reporting
mipi_dsi_host_ll_enable_rx_crc(hal->host, true);
mipi_dsi_host_ll_enable_rx_ecc(hal->host, true);
// enable sending the EoTp packet at the end of each transmission
mipi_dsi_host_ll_enable_tx_eotp(hal->host, true, true);
// Set the divider to get the Time Out clock, clock source is the high-speed byte clock
mipi_dsi_host_ll_set_timeout_clock_division(hal->host, bus_config->lane_bit_rate_mbps / 8 / MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ);
// Set the divider to get the TX Escape clock, clock source is the high-speed byte clock
mipi_dsi_host_ll_set_escape_clock_division(hal->host, bus_config->lane_bit_rate_mbps / 8 / MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ);
// set the timeout intervals to zero, means to disable the timeout mechanism
mipi_dsi_host_ll_set_timeout_count(hal->host, 0, 0, 0, 0, 0, 0, 0);
// DSI host will wait indefinitely for a read response from the DSI device
mipi_dsi_phy_ll_set_max_read_time(hal->host, 6000);
// set how long the DSI host will wait before sending the next transmission
mipi_dsi_phy_ll_set_stop_wait_time(hal->host, 0x3F);
*ret_bus = dsi_bus;
return ESP_OK;
err:
if (dsi_bus) {
esp_lcd_del_dsi_bus(dsi_bus);
}
return ret;
}
esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus)
{
ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
int bus_id = bus->bus_id;
// disable the clock source for DSI PHY
DSI_CLOCK_SRC_ATOMIC() {
mipi_dsi_ll_enable_phy_reference_clock(bus_id, false);
mipi_dsi_ll_enable_phy_config_clock(bus_id, false);
}
// disable the APB clock for accessing the DSI peripheral registers
DSI_RCC_ATOMIC() {
mipi_dsi_ll_enable_bus_clock(bus_id, false);
}
if (bus->pm_lock) {
esp_pm_lock_release(bus->pm_lock);
esp_pm_lock_delete(bus->pm_lock);
}
free(bus);
return ESP_OK;
}