/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2016 Scott Shawcroft * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "shared-bindings/busio/SPI.h" #include "py/mperrno.h" #include "py/runtime.h" #include "boards/board.h" #include "shared-bindings/microcontroller/Pin.h" #include "supervisor/shared/rgb_led_status.h" static bool spi_never_reset[SOC_SPI_PERIPH_NUM]; // Store one lock handle per device so that we can free it. static spi_bus_lock_dev_handle_t lock_dev_handle[SOC_SPI_PERIPH_NUM]; static intr_handle_t intr_handle[SOC_SPI_PERIPH_NUM]; void spi_reset(void) { for (spi_host_device_t host_id = SPI2_HOST; host_id < SOC_SPI_PERIPH_NUM; host_id++) { if (spi_never_reset[host_id]) { continue; } bool in_use = false; if (lock_dev_handle[host_id] != NULL) { spi_bus_lock_unregister_dev(lock_dev_handle[host_id]); lock_dev_handle[host_id] = NULL; in_use = true; } if (intr_handle[host_id] != NULL) { esp_intr_free(intr_handle[host_id]); intr_handle[host_id] = NULL; in_use = true; } if (in_use) { spi_bus_free(host_id); } } } // This is copied in from the ESP-IDF because it is static. // Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. static bool bus_uses_iomux_pins(spi_host_device_t host, const spi_bus_config_t* bus_config) { if (bus_config->sclk_io_num>=0 && bus_config->sclk_io_num != spi_periph_signal[host].spiclk_iomux_pin) return false; if (bus_config->quadwp_io_num>=0 && bus_config->quadwp_io_num != spi_periph_signal[host].spiwp_iomux_pin) return false; if (bus_config->quadhd_io_num>=0 && bus_config->quadhd_io_num != spi_periph_signal[host].spihd_iomux_pin) return false; if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num != spi_periph_signal[host].spid_iomux_pin) return false; if (bus_config->miso_io_num>=0 && bus_config->miso_io_num != spi_periph_signal[host].spiq_iomux_pin) return false; return true; } // End copied code. static bool spi_bus_is_free(spi_host_device_t host_id) { return spi_bus_get_attr(host_id) == NULL; } static void spi_interrupt_handler(void *arg) { // busio_spi_obj_t *self = arg; } // The interrupt may get invoked by the bus lock. static void spi_bus_intr_enable(void *self) { esp_intr_enable(((busio_spi_obj_t *)self)->interrupt); } // The interrupt is always disabled by the ISR itself, not exposed static void spi_bus_intr_disable(void *self) { esp_intr_disable(((busio_spi_obj_t *)self)->interrupt); } void common_hal_busio_spi_construct(busio_spi_obj_t *self, const mcu_pin_obj_t * clock, const mcu_pin_obj_t * mosi, const mcu_pin_obj_t * miso) { spi_bus_config_t bus_config; bus_config.mosi_io_num = mosi != NULL ? mosi->number : -1; bus_config.miso_io_num = miso != NULL ? miso->number : -1; bus_config.sclk_io_num = clock != NULL ? clock->number : -1; bus_config.quadwp_io_num = -1; bus_config.quadhd_io_num = -1; bus_config.max_transfer_sz = 0; // Uses the default bus_config.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | (mosi != NULL ? SPICOMMON_BUSFLAG_MOSI : 0) | (miso != NULL ? SPICOMMON_BUSFLAG_MISO : 0); bus_config.intr_flags = 0; // RAM and Flash is often on SPI1 and is unsupported by the IDF so use it as // a flag value. spi_host_device_t host_id = SPI1_HOST; self->connected_through_gpio = true; // Try and save SPI2 for pins that are on the IOMUX if (bus_uses_iomux_pins(SPI2_HOST, &bus_config) && spi_bus_is_free(SPI2_HOST)) { host_id = SPI2_HOST; self->connected_through_gpio = false; } else if (spi_bus_is_free(SPI3_HOST)) { host_id = SPI3_HOST; } else if (spi_bus_is_free(SPI2_HOST)) { host_id = SPI2_HOST; } if (host_id == SPI1_HOST) { mp_raise_ValueError(translate("All SPI peripherals are in use")); } esp_err_t result = spi_bus_initialize(host_id, &bus_config, host_id /* dma channel */); if (result == ESP_ERR_NO_MEM) { mp_raise_msg(&mp_type_MemoryError, translate("ESP-IDF memory allocation failed")); } else if (result == ESP_ERR_INVALID_ARG) { mp_raise_ValueError(translate("Invalid pins")); } // After this point, we need to deinit to free IDF memory so fill out self's pins. self->clock_pin = clock; self->MOSI_pin = mosi; self->MISO_pin = miso; self->host_id = host_id; spi_bus_lock_dev_config_t config = { .flags = 0 }; // The returned lock is stored in the bus lock but must be freed separately with // spi_bus_lock_unregister_dev. result = spi_bus_lock_register_dev(spi_bus_get_attr(host_id)->lock, &config, &self->lock); if (result == ESP_ERR_NO_MEM) { common_hal_busio_spi_deinit(self); mp_raise_msg(&mp_type_MemoryError, translate("ESP-IDF memory allocation failed")); } lock_dev_handle[host_id] = self->lock; result = esp_intr_alloc(spicommon_irqsource_for_host(host_id), bus_config.intr_flags | ESP_INTR_FLAG_INTRDISABLED, spi_interrupt_handler, self, &self->interrupt); if (result == ESP_ERR_NO_MEM) { common_hal_busio_spi_deinit(self); mp_raise_msg(&mp_type_MemoryError, translate("ESP-IDF memory allocation failed")); } intr_handle[host_id] = self->interrupt; spi_bus_lock_set_bg_control(spi_bus_get_attr(host_id)->lock, spi_bus_intr_enable, spi_bus_intr_disable, self); spi_hal_context_t* hal = &self->hal_context; // spi_hal_init clears the given hal context so set everything after. spi_hal_init(hal, host_id); // We don't use native CS. // hal->cs_setup = 0; // hal->cs_hold = 0; // hal->cs_pin_id = 0; hal->sio = 0; hal->half_duplex = 0; // hal->tx_lsbfirst = 0; // hal->rx_lsbfirst = 0; hal->no_compensate = 1; // Ignore CS bits // We don't use cmd, addr or dummy bits. // hal->cmd = 0; // hal->cmd_bits = 0; // hal->addr_bits = 0; // hal->dummy_bits = 0; // hal->addr = 0; hal->io_mode = SPI_LL_IO_MODE_NORMAL; common_hal_busio_spi_configure(self, 250000, 0, 0, 8); } void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { spi_never_reset[self->host_id] = true; common_hal_never_reset_pin(self->clock_pin); if (self->MOSI_pin != NULL) { common_hal_never_reset_pin(self->MOSI_pin); } if (self->MISO_pin != NULL) { common_hal_never_reset_pin(self->MISO_pin); } } bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { return self->clock_pin == NULL; } void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { if (common_hal_busio_spi_deinited(self)) { return; } spi_never_reset[self->host_id] = false; if (self->lock != NULL) { spi_bus_lock_unregister_dev(self->lock); lock_dev_handle[self->host_id] = NULL; } if (self->interrupt != NULL) { esp_intr_free(self->interrupt); intr_handle[self->host_id] = NULL; } spi_bus_free(self->host_id); common_hal_reset_pin(self->clock_pin); if (self->MOSI_pin != NULL) { common_hal_reset_pin(self->MOSI_pin); } if (self->MISO_pin != NULL) { common_hal_reset_pin(self->MISO_pin); } self->clock_pin = NULL; self->MISO_pin = NULL; self->MOSI_pin = NULL; } bool common_hal_busio_spi_configure(busio_spi_obj_t *self, uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) { if (baudrate == self->target_frequency && polarity == self->polarity && phase == self->phase && bits == self->bits) { return true; } self->hal_context.mode = polarity << 1 | phase; self->polarity = polarity; self->phase = phase; self->bits = bits; self->target_frequency = baudrate; self->hal_context.timing_conf = &self->timing_conf; esp_err_t result = spi_hal_cal_clock_conf(&self->hal_context, self->target_frequency, 128 /* duty_cycle */, self->connected_through_gpio, 0 /* input_delay_ns */, &self->real_frequency, &self->timing_conf); if (result != ESP_OK) { return false; } spi_hal_setup_device(&self->hal_context); return true; } bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { // If our lock has already been taken then return false because someone else // may already grabbed it in our call stack. if (self->has_lock) { return false; } // Wait to grab the lock from another task. esp_err_t result = spi_bus_lock_acquire_start(self->lock, portMAX_DELAY); self->has_lock = result == ESP_OK; return self->has_lock; } bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) { return self->has_lock; } void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { spi_bus_lock_acquire_end(self->lock); self->has_lock = false; } bool common_hal_busio_spi_write(busio_spi_obj_t *self, const uint8_t *data, size_t len) { if (self->MOSI_pin == NULL) { mp_raise_ValueError(translate("No MOSI Pin")); } return common_hal_busio_spi_transfer(self, data, NULL, len); } bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value) { if (self->MISO_pin == NULL) { mp_raise_ValueError(translate("No MISO Pin")); } if (self->MOSI_pin == NULL) { return common_hal_busio_spi_transfer(self, NULL, data, len); } else { memset(data, write_value, len); return common_hal_busio_spi_transfer(self, data, data, len); } } bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) { if (len == 0) { return true; } // Other than the read special case, stop transfers that don't have a pin/array match if (!self->MOSI_pin && (data_out != data_in)) { mp_raise_ValueError(translate("No MOSI Pin")); } if (!self->MISO_pin && data_in) { mp_raise_ValueError(translate("No MISO Pin")); } spi_hal_context_t* hal = &self->hal_context; hal->send_buffer = NULL; hal->rcv_buffer = NULL; // Reset timing_conf in case we've moved since the last time we used it. hal->timing_conf = &self->timing_conf; lldesc_t tx_dma __attribute__((aligned(16))); lldesc_t rx_dma __attribute__((aligned(16))); hal->dmadesc_tx = &tx_dma; hal->dmadesc_rx = &rx_dma; hal->dmadesc_n = 1; size_t burst_length; // If both of the incoming pointers are DMA capable then use DMA. Otherwise, do // bursts the size of the SPI data buffer without DMA. if ((data_out == NULL || esp_ptr_dma_capable(data_out)) && (data_in == NULL || esp_ptr_dma_capable(data_out))) { hal->dma_enabled = 1; burst_length = LLDESC_MAX_NUM_PER_DESC; } else { hal->dma_enabled = 0; burst_length = sizeof(hal->hw->data_buf); // When switching to non-DMA, we need to make sure DMA is off. Otherwise, // the S2 will transmit zeroes instead of our data. spi_ll_txdma_disable(hal->hw); } // This rounds up. size_t burst_count = (len + burst_length - 1) / burst_length; for (size_t i = 0; i < burst_count; i++) { size_t offset = burst_length * i; size_t this_length = len - offset; if (this_length > burst_length) { this_length = burst_length; } hal->tx_bitlen = this_length * self->bits; hal->rx_bitlen = this_length * self->bits; if (data_out != NULL) { hal->send_buffer = (uint8_t*) data_out + offset; } if (data_in != NULL) { hal->rcv_buffer = data_in + offset; } spi_hal_setup_trans(hal); spi_hal_prepare_data(hal); spi_hal_user_start(hal); // TODO: Switch to waiting on a lock that is given by an interrupt. while (!spi_hal_usr_is_done(hal)) { RUN_BACKGROUND_TASKS; } spi_hal_fetch_result(hal); } hal->dmadesc_tx = NULL; hal->dmadesc_rx = NULL; hal->dmadesc_n = 0; return true; } uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t* self) { return self->real_frequency; } uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t* self) { return self->phase; } uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t* self) { return self->polarity; }