diff --git a/arch/x86/soc/quark_se/Kconfig b/arch/x86/soc/quark_se/Kconfig index 4502e0b37cc..1b0a710edfc 100644 --- a/arch/x86/soc/quark_se/Kconfig +++ b/arch/x86/soc/quark_se/Kconfig @@ -191,6 +191,7 @@ endif if SPI config SPI_DW def_bool y +if SPI_DW config SPI_DW_CLOCK_GATE def_bool n config SPI_DW_CLOCK_GATE_DRV_NAME @@ -215,6 +216,21 @@ config SPI_DW_PORT_1_REGS default 0xb0001400 config SPI_DW_PORT_1_IRQ default 3 +endif # SPI_DW + +config SPI_QMSI + def_bool n + +if SPI_QMSI +config SPI_QMSI_PORT_0 + def_bool y +config SPI_QMSI_PORT_0_IRQ + default 2 +config SPI_QMSI_PORT_1 + def_bool y +config SPI_QMSI_PORT_1_IRQ + default 3 +endif # SPI_QMSI endif if WATCHDOG diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 5a0e75927a6..776b08c54b1 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -239,6 +239,7 @@ config SPI_DW_CS_GPIO config SPI_DW_INIT_PRIORITY int "Init priority" + depends on SPI_DW default 60 help Device driver initialization priority. @@ -376,4 +377,65 @@ config SPI_DW_PORT_1_PRI depends on SPI_DW_PORT_1 default 2 +config SPI_QMSI + bool "QMSI driver for SPI controller" + depends on SPI && QMSI_DRIVERS + default n + help + SPI driver implementation using QMSI library. QMSI is the + Quark Microcontroller Software Interface, providing a common + interface to the Quark family of microcontrollers. + +config SPI_QMSI_PORT_0 + bool + prompt "QMSI SPI port 0" + depends on SPI_QMSI + default n + help + Enable QMSI's SPI controller port 0. + +config SPI_QMSI_PORT_0_DRV_NAME + string + prompt "QMSI SPI port 0 device name" + depends on SPI_QMSI_PORT_0 + default "spi_0" + +config SPI_QMSI_PORT_0_IRQ + int + prompt "Port 0 interrupt" + depends on SPI_QMSI_PORT_0 + default 0 + +config SPI_QMSI_PORT_0_PRI + int + prompt "Port 0 interrupt priority" + depends on SPI_QMSI_PORT_0 + default 2 + +config SPI_QMSI_PORT_1 + bool + prompt "QMSI SPI port 1" + depends on SPI_QMSI + default n + help + Enable QMSI's SPI controller port 1. + +config SPI_QMSI_PORT_1_DRV_NAME + string + prompt "QMSI SPI port 1 device name" + depends on SPI_QMSI_PORT_1 + default "spi_1" + +config SPI_QMSI_PORT_1_IRQ + int + prompt "Port 1 interrupt" + depends on SPI_QMSI_PORT_1 + default 0 + +config SPI_QMSI_PORT_1_PRI + int + prompt "Port 0 interrupt priority" + depends on SPI_QMSI_PORT_1 + default 2 + endif diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index f4bd71660cc..2a0ea7c9e1b 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -1,2 +1,4 @@ +ccflags-$(CONFIG_SPI_QMSI) +=-I$(CONFIG_QMSI_INSTALL_PATH)/include obj-$(CONFIG_SPI_INTEL) += spi_intel.o obj-$(CONFIG_SPI_DW) += spi_dw.o +obj-$(CONFIG_SPI_QMSI) += spi_qmsi.o diff --git a/drivers/spi/spi_qmsi.c b/drivers/spi/spi_qmsi.c new file mode 100644 index 00000000000..0fd02c01eac --- /dev/null +++ b/drivers/spi/spi_qmsi.c @@ -0,0 +1,271 @@ +/* + * 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 +#include +#include +#include +#include + +#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; +}; + +struct spi_qmsi_runtime { + 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 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; + + 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, + uint8_t *tx_buf, uint32_t tx_buf_len, + uint8_t *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; + + rc = qm_spi_irq_transfer(spi, xfer); + if (rc != QM_RC_OK) + 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 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; + 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; + default: + return DEV_FAIL; + } + + 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, +}; + +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_KERNEL_INIT_PRIORITY_DEFAULT); + +#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, +}; + +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_KERNEL_INIT_PRIORITY_DEFAULT); + +#endif /* CONFIG_SPI_QMSI_PORT_1 */