| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * |
| * 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. |
| */ |
| |
| #include <device.h> |
| #include <drivers/ioapic.h> |
| #include <init.h> |
| #include <nanokernel.h> |
| #include <spi.h> |
| #include <gpio.h> |
| |
| #include "qm_scss.h" |
| #include "qm_spi.h" |
| |
| struct pending_transfer { |
| struct device *dev; |
| qm_spi_async_transfer_t xfer; |
| int counter; |
| }; |
| |
| static struct pending_transfer pending_transfers[QM_SPI_NUM]; |
| |
| struct spi_qmsi_config { |
| qm_spi_t spi; |
| char *cs_port; |
| uint32_t cs_pin; |
| }; |
| |
| struct spi_qmsi_runtime { |
| struct device *gpio_cs; |
| device_sync_call_t sync; |
| qm_spi_config_t cfg; |
| qm_rc_t rc; |
| bool loopback; |
| }; |
| |
| static inline qm_spi_bmode_t config_to_bmode(uint8_t mode) |
| { |
| switch (mode) { |
| case SPI_MODE_CPHA: |
| return QM_SPI_BMODE_1; |
| case SPI_MODE_CPOL: |
| return QM_SPI_BMODE_2; |
| case SPI_MODE_CPOL | SPI_MODE_CPHA: |
| return QM_SPI_BMODE_3; |
| default: |
| return QM_SPI_BMODE_0; |
| } |
| } |
| |
| static void spi_control_cs(struct device *dev, bool active) |
| { |
| struct spi_qmsi_runtime *context = dev->driver_data; |
| struct spi_qmsi_config *config = dev->config->config_info; |
| struct device *gpio = context->gpio_cs; |
| |
| if (!gpio) |
| return; |
| |
| gpio_pin_write(gpio, config->cs_pin, !active); |
| } |
| |
| static int spi_qmsi_configure(struct device *dev, |
| struct spi_config *config) |
| { |
| struct spi_qmsi_runtime *context = dev->driver_data; |
| qm_spi_config_t *cfg = &context->cfg; |
| |
| cfg->frame_size = SPI_WORD_SIZE_GET(config->config) - 1; |
| cfg->bus_mode = config_to_bmode(SPI_MODE(config->config)); |
| /* As loopback is implemented inside the controller, |
| * the bus mode doesn't matter. |
| */ |
| context->loopback = SPI_MODE(config->config) & SPI_MODE_LOOP; |
| cfg->clk_divider = config->max_sys_freq; |
| |
| /* Will set the configuration before the transfer starts */ |
| return DEV_OK; |
| } |
| |
| static void pending_transfer_complete(uint32_t id, qm_rc_t rc) |
| { |
| struct pending_transfer *pending = &pending_transfers[id]; |
| struct device *dev = pending->dev; |
| struct spi_qmsi_runtime *context; |
| qm_spi_config_t *cfg; |
| |
| if (!dev) |
| return; |
| |
| context = dev->driver_data; |
| cfg = &context->cfg; |
| |
| pending->counter++; |
| |
| /* |
| * When it is TX/RX transfer this function will be called twice. |
| */ |
| if (cfg->transfer_mode == QM_SPI_TMOD_TX_RX && pending->counter == 1) |
| return; |
| |
| spi_control_cs(dev, false); |
| |
| pending->dev = NULL; |
| pending->counter = 0; |
| context->rc = rc; |
| device_sync_call_complete(&context->sync); |
| } |
| |
| static void spi_qmsi_tx_callback(uint32_t id, uint32_t len) |
| { |
| pending_transfer_complete(id, QM_RC_OK); |
| } |
| |
| static void spi_qmsi_rx_callback(uint32_t id, uint32_t len) |
| { |
| pending_transfer_complete(id, QM_RC_OK); |
| } |
| |
| static void spi_qmsi_err_callback(uint32_t id, qm_rc_t err) |
| { |
| pending_transfer_complete(id, err); |
| } |
| |
| static int spi_qmsi_slave_select(struct device *dev, uint32_t slave) |
| { |
| struct spi_qmsi_config *spi_config = dev->config->config_info; |
| qm_spi_t spi = spi_config->spi; |
| |
| return qm_spi_slave_select(spi, 1 << (slave - 1)) ? DEV_FAIL : DEV_OK; |
| } |
| |
| static inline uint8_t frame_size_to_dfs(qm_spi_frame_size_t frame_size) |
| { |
| if (frame_size <= QM_SPI_FRAME_SIZE_8_BIT) |
| return 1; |
| if (frame_size <= QM_SPI_FRAME_SIZE_16_BIT) |
| return 2; |
| if (frame_size <= QM_SPI_FRAME_SIZE_32_BIT) |
| return 4; |
| |
| /* This should never happen, it will crash later on. */ |
| return 0; |
| } |
| |
| static int spi_qmsi_transceive(struct device *dev, |
| const void *tx_buf, uint32_t tx_buf_len, |
| void *rx_buf, uint32_t rx_buf_len) |
| { |
| struct spi_qmsi_config *spi_config = dev->config->config_info; |
| qm_spi_t spi = spi_config->spi; |
| struct spi_qmsi_runtime *context = dev->driver_data; |
| qm_spi_config_t *cfg = &context->cfg; |
| uint8_t dfs = frame_size_to_dfs(cfg->frame_size); |
| qm_spi_async_transfer_t *xfer; |
| qm_rc_t rc; |
| |
| if (pending_transfers[spi].dev) |
| return DEV_USED; |
| |
| pending_transfers[spi].dev = dev; |
| xfer = &pending_transfers[spi].xfer; |
| |
| xfer->rx = rx_buf; |
| xfer->rx_len = rx_buf_len / dfs; |
| xfer->tx = tx_buf; |
| xfer->tx_len = tx_buf_len / dfs; |
| xfer->id = spi; |
| xfer->tx_callback = spi_qmsi_tx_callback; |
| xfer->rx_callback = spi_qmsi_rx_callback; |
| xfer->err_callback = spi_qmsi_err_callback; |
| |
| if (tx_buf_len == 0) |
| cfg->transfer_mode = QM_SPI_TMOD_RX; |
| else if (rx_buf_len == 0) |
| cfg->transfer_mode = QM_SPI_TMOD_TX; |
| else { |
| /* FIXME: QMSI expects rx_buf_len and tx_buf_len to |
| * have the same size. |
| */ |
| cfg->transfer_mode = QM_SPI_TMOD_TX_RX; |
| } |
| |
| if (context->loopback) |
| QM_SPI[spi]->ctrlr0 |= BIT(11); |
| |
| rc = qm_spi_set_config(spi, cfg); |
| if (rc != QM_RC_OK) |
| return DEV_INVALID_CONF; |
| |
| spi_control_cs(dev, true); |
| |
| rc = qm_spi_irq_transfer(spi, xfer); |
| if (rc != QM_RC_OK) { |
| spi_control_cs(dev, false); |
| return DEV_FAIL; |
| } |
| |
| device_sync_call_wait(&context->sync); |
| |
| return context->rc ? DEV_FAIL : DEV_OK; |
| } |
| |
| static int spi_qmsi_suspend(struct device *dev) |
| { |
| /* FIXME */ |
| return 0; |
| } |
| |
| static int spi_qmsi_resume(struct device *dev) |
| { |
| /* FIXME */ |
| return 0; |
| } |
| |
| static struct spi_driver_api spi_qmsi_api = { |
| .configure = spi_qmsi_configure, |
| .slave_select = spi_qmsi_slave_select, |
| .transceive = spi_qmsi_transceive, |
| .suspend = spi_qmsi_suspend, |
| .resume = spi_qmsi_resume, |
| }; |
| |
| static struct device *gpio_cs_init(struct spi_qmsi_config *config) |
| { |
| struct device *gpio; |
| |
| if (!config->cs_port) |
| return NULL; |
| |
| gpio = device_get_binding(config->cs_port); |
| if (!gpio) |
| return NULL; |
| |
| gpio_pin_configure(gpio, config->cs_pin, GPIO_DIR_OUT); |
| gpio_pin_write(gpio, config->cs_pin, 1); |
| |
| return gpio; |
| } |
| |
| static int spi_qmsi_init(struct device *dev) |
| { |
| struct spi_qmsi_config *spi_config = dev->config->config_info; |
| struct spi_qmsi_runtime *context = dev->driver_data; |
| |
| dev->driver_api = &spi_qmsi_api; |
| |
| switch (spi_config->spi) { |
| case QM_SPI_MST_0: |
| IRQ_CONNECT(CONFIG_SPI_QMSI_PORT_0_IRQ, |
| CONFIG_SPI_QMSI_PORT_0_PRI, qm_spi_master_0_isr, |
| 0, IOAPIC_LEVEL | IOAPIC_HIGH); |
| irq_enable(CONFIG_SPI_QMSI_PORT_0_IRQ); |
| clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_SPI_M0_REGISTER); |
| QM_SCSS_INT->int_spi_mst_0_mask &= ~BIT(0); |
| break; |
| |
| #ifdef CONFIG_SPI_QMSI_PORT_1 |
| case QM_SPI_MST_1: |
| IRQ_CONNECT(CONFIG_SPI_QMSI_PORT_1_IRQ, |
| CONFIG_SPI_QMSI_PORT_1_PRI, qm_spi_master_1_isr, |
| 0, IOAPIC_LEVEL | IOAPIC_HIGH); |
| irq_enable(CONFIG_SPI_QMSI_PORT_1_IRQ); |
| clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_SPI_M1_REGISTER); |
| QM_SCSS_INT->int_spi_mst_1_mask &= ~BIT(0); |
| break; |
| #endif /* CONFIG_SPI_QMSI_PORT_1 */ |
| |
| default: |
| return DEV_FAIL; |
| } |
| |
| context->gpio_cs = gpio_cs_init(spi_config); |
| |
| device_sync_call_init(&context->sync); |
| |
| return DEV_OK; |
| } |
| |
| #ifdef CONFIG_SPI_QMSI_PORT_0 |
| static struct spi_qmsi_config spi_qmsi_mst_0_config = { |
| .spi = QM_SPI_MST_0, |
| #ifdef CONFIG_SPI_QMSI_CS_GPIO |
| .cs_port = CONFIG_SPI_QMSI_PORT_0_CS_GPIO_PORT, |
| .cs_pin = CONFIG_SPI_QMSI_PORT_0_CS_GPIO_PIN, |
| #endif |
| }; |
| |
| static struct spi_qmsi_runtime spi_qmsi_mst_0_runtime; |
| |
| DEVICE_INIT(spi_master_0, CONFIG_SPI_QMSI_PORT_0_DRV_NAME, |
| spi_qmsi_init, &spi_qmsi_mst_0_runtime, &spi_qmsi_mst_0_config, |
| SECONDARY, CONFIG_SPI_QMSI_INIT_PRIORITY); |
| |
| |
| #endif /* CONFIG_SPI_QMSI_PORT_0 */ |
| #ifdef CONFIG_SPI_QMSI_PORT_1 |
| |
| static struct spi_qmsi_config spi_qmsi_mst_1_config = { |
| .spi = QM_SPI_MST_1, |
| #ifdef CONFIG_SPI_QMSI_CS_GPIO |
| .cs_port = CONFIG_SPI_QMSI_PORT_1_CS_GPIO_PORT, |
| .cs_pin = CONFIG_SPI_QMSI_PORT_1_CS_GPIO_PIN, |
| #endif |
| }; |
| |
| static struct spi_qmsi_runtime spi_qmsi_mst_1_runtime; |
| |
| DEVICE_INIT(spi_master_1, CONFIG_SPI_QMSI_PORT_1_DRV_NAME, |
| spi_qmsi_init, &spi_qmsi_mst_1_runtime, &spi_qmsi_mst_1_config, |
| SECONDARY, CONFIG_SPI_QMSI_INIT_PRIORITY); |
| |
| #endif /* CONFIG_SPI_QMSI_PORT_1 */ |