From 5148c98e835d6b220fc1b70c9f60fd12d241bdbe Mon Sep 17 00:00:00 2001 From: cyliang tw Date: Tue, 25 Jul 2023 11:38:02 +0800 Subject: [PATCH] drivers: spi: support for Nuvoton numaker series Add Nuvoton numaker series spi controller, including full and half duplex support. Signed-off-by: cyliang tw --- drivers/spi/CMakeLists.txt | 1 + drivers/spi/Kconfig | 2 + drivers/spi/Kconfig.numaker | 14 + drivers/spi/spi_numaker.c | 351 ++++++++++++++++++++++ dts/arm/nuvoton/m46x.dtsi | 121 ++++++++ dts/bindings/spi/nuvoton,numaker-spi.yaml | 21 ++ 6 files changed, 510 insertions(+) create mode 100644 drivers/spi/Kconfig.numaker create mode 100644 drivers/spi/spi_numaker.c create mode 100644 dts/bindings/spi/nuvoton,numaker-spi.yaml diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 2eb499366ea..6b855957db0 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -40,6 +40,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_XMC4XXX spi_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_SPI_PW spi_pw.c) zephyr_library_sources_ifdef(CONFIG_SPI_SMARTBOND spi_smartbond.c) zephyr_library_sources_ifdef(CONFIG_SPI_OPENTITAN spi_opentitan.c) +zephyr_library_sources_ifdef(CONFIG_SPI_NUMAKER spi_numaker.c) zephyr_library_sources_ifdef(CONFIG_SPI_RTIO spi_rtio.c) zephyr_library_sources_ifdef(CONFIG_SPI_ASYNC spi_signal.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 03d9afba1fe..71a658c3e88 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -127,4 +127,6 @@ source "drivers/spi/Kconfig.smartbond" source "drivers/spi/Kconfig.opentitan" +source "drivers/spi/Kconfig.numaker" + endif # SPI diff --git a/drivers/spi/Kconfig.numaker b/drivers/spi/Kconfig.numaker new file mode 100644 index 00000000000..2467c146fbf --- /dev/null +++ b/drivers/spi/Kconfig.numaker @@ -0,0 +1,14 @@ +# NUMAKER SPI Driver configuration options + +# Copyright (c) 2023 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config SPI_NUMAKER + bool "Nuvoton NuMaker MCU SPI driver" + default y + select HAS_NUMAKER_SPI + depends on DT_HAS_NUVOTON_NUMAKER_SPI_ENABLED + help + This option enables the SPI driver for Nuvoton NuMaker family of + processors. + Say y if you wish to enable NuMaker SPI. diff --git a/drivers/spi/spi_numaker.c b/drivers/spi/spi_numaker.c new file mode 100644 index 00000000000..372f96271ad --- /dev/null +++ b/drivers/spi/spi_numaker.c @@ -0,0 +1,351 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2023 Nuvoton Technology Corporation. + */ + +#define DT_DRV_COMPAT nuvoton_numaker_spi + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(spi_numaker, CONFIG_SPI_LOG_LEVEL); + +#include "spi_context.h" +#include + +#define SPI_NUMAKER_TX_NOP 0x00 + +struct spi_numaker_config { + SPI_T *spi; + bool is_qspi; + const struct reset_dt_spec reset; + /* clock configuration */ + uint32_t clk_modidx; + uint32_t clk_src; + uint32_t clk_div; + const struct device *clk_dev; + const struct pinctrl_dev_config *pincfg; +}; + +struct spi_numaker_data { + struct spi_context ctx; +}; + +/* + * CPOL/CPHA = 0/0 --> SPI_MODE_0 + * CPOL/CPHA = 0/1 --> SPI_MODE_1 + * CPOL/CPHA = 1/0 --> SPI_MODE_2 + * CPOL/CPHA = 1/1 --> SPI_MODE_3 + */ +static const uint32_t smode_tbl[4] = { + SPI_MODE_0, SPI_MODE_1, SPI_MODE_2, SPI_MODE_3 +}; + +static const uint32_t qsmode_tbl[4] = { + QSPI_MODE_0, QSPI_MODE_1, QSPI_MODE_2, QSPI_MODE_3 +}; + +static int spi_numaker_configure(const struct device *dev, const struct spi_config *config) +{ + int mode; + struct spi_numaker_data *data = dev->data; + const struct spi_numaker_config *dev_cfg = dev->config; + + LOG_DBG("%s", __func__); + if (spi_context_configured(&data->ctx, config)) { + return 0; + } + + if (SPI_MODE_GET(config->operation) & SPI_MODE_LOOP) { + LOG_ERR("Loop back mode not support"); + return -ENOTSUP; + } + + if (SPI_OP_MODE_GET(config->operation) == SPI_OP_MODE_SLAVE) { + LOG_ERR("Slave mode not support"); + return -ENOTSUP; + } + + /* Clear FIFO */ + SPI_ClearRxFIFO(dev_cfg->spi); + SPI_ClearTxFIFO(dev_cfg->spi); + + if (SPI_MODE_GET(config->operation) & SPI_MODE_CPOL) { + mode = (SPI_MODE_GET(config->operation) & SPI_MODE_CPHA) ? 3 : 2; + } else { + mode = (SPI_MODE_GET(config->operation) & SPI_MODE_CPHA) ? 1 : 0; + } + + /* Make SPI module be ready to transfer */ + if (dev_cfg->is_qspi) { + QSPI_Open((QSPI_T *)dev_cfg->spi, + (SPI_OP_MODE_GET(config->operation) == SPI_OP_MODE_SLAVE) ? QSPI_SLAVE + : QSPI_MASTER, + qsmode_tbl[mode], + SPI_WORD_SIZE_GET(config->operation), config->frequency); + } else { + SPI_Open(dev_cfg->spi, + (SPI_OP_MODE_GET(config->operation) == SPI_OP_MODE_SLAVE) ? SPI_SLAVE + : SPI_MASTER, + smode_tbl[mode], + SPI_WORD_SIZE_GET(config->operation), config->frequency); + } + + /* Set Transfer LSB or MSB first */ + if ((config->operation) & SPI_TRANSFER_LSB) { + SPI_SET_LSB_FIRST(dev_cfg->spi); + } else { + SPI_SET_MSB_FIRST(dev_cfg->spi); + } + + /* full/half duplex */ + if (config->operation & SPI_HALF_DUPLEX) { + /* half duplex, which results in 3-wire usage */ + SPI_ENABLE_3WIRE_MODE(dev_cfg->spi); + } else { + /* full duplex */ + SPI_DISABLE_3WIRE_MODE(dev_cfg->spi); + } + + /* Active high CS logic */ + if (config->operation & SPI_CS_ACTIVE_HIGH) { + SPI_SET_SS_HIGH(dev_cfg->spi); + } else { + SPI_SET_SS_LOW(dev_cfg->spi); + } + + /* Enable the automatic hardware slave select function. Select the SS pin and configure as + * low-active. + */ + if (data->ctx.num_cs_gpios != 0) { + SPI_EnableAutoSS(dev_cfg->spi, SPI_SS, SPI_SS_ACTIVE_LOW); + } else { + SPI_DisableAutoSS(dev_cfg->spi); + } + + /* Be able to set TX/RX FIFO threshold, for ex: SPI_SetFIFO(dev_cfg->spi, 2, 2) */ + + data->ctx.config = config; + + return 0; +} + +static int spi_numaker_txrx(const struct device *dev) +{ + struct spi_numaker_data *data = dev->data; + const struct spi_numaker_config *dev_cfg = dev->config; + struct spi_context *ctx = &data->ctx; + uint32_t tx_frame, rx_frame; + uint8_t word_size, spi_dfs; + uint32_t time_out_cnt; + + LOG_DBG("%s", __func__); + word_size = SPI_WORD_SIZE_GET(ctx->config->operation); + + switch (word_size) { + case 8: + spi_dfs = 1; + break; + case 16: + spi_dfs = 2; + break; + case 24: + spi_dfs = 3; + break; + case 32: + spi_dfs = 4; + break; + default: + spi_dfs = 0; + LOG_ERR("Not support SPI WORD size as [%d] bits", word_size); + return -EIO; + } + + LOG_DBG("%s -->word_size [%d]", __func__, word_size); + + if (spi_context_tx_on(ctx)) { + tx_frame = ((ctx->tx_buf == NULL) ? SPI_NUMAKER_TX_NOP + : UNALIGNED_GET((uint8_t *)(data->ctx.tx_buf))); + /* Write to TX register */ + SPI_WRITE_TX(dev_cfg->spi, tx_frame); + spi_context_update_tx(ctx, spi_dfs, 1); + + /* Check SPI busy status */ + time_out_cnt = SystemCoreClock; /* 1 second time-out */ + while (SPI_IS_BUSY(dev_cfg->spi)) { + if (--time_out_cnt == 0) { + LOG_ERR("Wait for SPI time-out"); + return -EIO; + } + } + + LOG_DBG("%s --> TX [0x%x] done", __func__, tx_frame); + } + + /* Read received data */ + if (spi_context_rx_on(ctx)) { + if (SPI_GET_RX_FIFO_COUNT(dev_cfg->spi) > 0) { + rx_frame = SPI_READ_RX(dev_cfg->spi); + if (ctx->rx_buf != NULL) { + UNALIGNED_PUT(rx_frame, (uint8_t *)data->ctx.rx_buf); + } + spi_context_update_rx(ctx, spi_dfs, 1); + LOG_DBG("%s --> RX [0x%x] done", __func__, rx_frame); + } + } + + LOG_DBG("%s --> exit", __func__); + return 0; +} + +/* Remain TX/RX Data in spi_context TX/RX buffer */ +static bool spi_numaker_remain_words(struct spi_numaker_data *data) +{ + return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx); +} + +static int spi_numaker_transceive(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + struct spi_numaker_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + const struct spi_numaker_config *dev_cfg = dev->config; + int ret; + + LOG_DBG("%s", __func__); + spi_context_lock(ctx, false, NULL, NULL, config); + ctx->config = config; + + ret = spi_numaker_configure(dev, config); + if (ret < 0) { + goto done; + } + + SPI_ENABLE(dev_cfg->spi); + + spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); + + /* if cs is defined: software cs control, set active true */ + if (spi_cs_is_gpio(config)) { + spi_context_cs_control(&data->ctx, true); + } + + /* transceive tx/rx data */ + do { + ret = spi_numaker_txrx(dev); + if (ret < 0) { + break; + } + } while (spi_numaker_remain_words(data)); + + /* if cs is defined: software cs control, set active false */ + if (spi_cs_is_gpio(config)) { + spi_context_cs_control(&data->ctx, false); + } + SPI_DISABLE(dev_cfg->spi); + +done: + spi_context_release(ctx, ret); + LOG_DBG("%s --> [%d]", __func__, ret); + return ret; +} + +static int spi_numaker_release(const struct device *dev, const struct spi_config *config) +{ + struct spi_numaker_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + + if (!spi_context_configured(ctx, config)) { + return -EINVAL; + } + spi_context_unlock_unconditionally(ctx); + + return 0; +} + +static struct spi_driver_api spi_numaker_driver_api = {.transceive = spi_numaker_transceive, + .release = spi_numaker_release}; + +static int spi_numaker_init(const struct device *dev) +{ + struct spi_numaker_data *data = dev->data; + const struct spi_numaker_config *dev_cfg = dev->config; + int err = 0; + struct numaker_scc_subsys scc_subsys; + + SYS_UnlockReg(); + + /* CLK controller */ + memset(&scc_subsys, 0x00, sizeof(scc_subsys)); + scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC; + scc_subsys.pcc.clk_modidx = dev_cfg->clk_modidx; + scc_subsys.pcc.clk_src = dev_cfg->clk_src; + scc_subsys.pcc.clk_div = dev_cfg->clk_div; + + /* Equivalent to CLK_EnableModuleClock() */ + err = clock_control_on(dev_cfg->clk_dev, (clock_control_subsys_t)&scc_subsys); + if (err != 0) { + goto done; + } + /* Equivalent to CLK_SetModuleClock() */ + err = clock_control_configure(dev_cfg->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL); + if (err != 0) { + goto done; + } + + err = pinctrl_apply_state(dev_cfg->pincfg, PINCTRL_STATE_DEFAULT); + if (err) { + LOG_ERR("Failed to apply pinctrl state"); + goto done; + } + + err = spi_context_cs_configure_all(&data->ctx); + if (err < 0) { + goto done; + } + + spi_context_unlock_unconditionally(&data->ctx); + + /* Reset this module, same as BSP's SYS_ResetModule(id_rst) */ + if (!device_is_ready(dev_cfg->reset.dev)) { + LOG_ERR("reset controller not ready"); + err = -ENODEV; + goto done; + } + + /* Reset SPI to default state */ + reset_line_toggle_dt(&dev_cfg->reset); + +done: + SYS_LockReg(); + return err; +} + +#define NUMAKER_SPI_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + static struct spi_numaker_data spi_numaker_data_##inst = { \ + SPI_CONTEXT_INIT_LOCK(spi_numaker_data_##inst, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_numaker_data_##inst, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(inst), ctx)}; \ + static struct spi_numaker_config spi_numaker_config_##inst = { \ + .spi = (SPI_T *)DT_INST_REG_ADDR(inst), \ + .is_qspi = DT_INST_NODE_HAS_PROP(inst, qspi), \ + .reset = RESET_DT_SPEC_INST_GET(inst), \ + .clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \ + .clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \ + .clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \ + .clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))), \ + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, &spi_numaker_init, NULL, &spi_numaker_data_##inst, \ + &spi_numaker_config_##inst, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ + &spi_numaker_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(NUMAKER_SPI_INIT) diff --git a/dts/arm/nuvoton/m46x.dtsi b/dts/arm/nuvoton/m46x.dtsi index b018032cc82..3d2b3956a25 100644 --- a/dts/arm/nuvoton/m46x.dtsi +++ b/dts/arm/nuvoton/m46x.dtsi @@ -283,6 +283,127 @@ status = "disabled"; interrupts = <61 2>; }; + + spi0: spi@40061000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40061000 0x6c>; + interrupts = <23 0>; + resets = <&rst NUMAKER_SPI0_RST>; + clocks = <&pcc NUMAKER_SPI0_MODULE NUMAKER_CLK_CLKSEL2_SPI0SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi1: spi@40062000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40062000 0x6c>; + interrupts = <51 0>; + resets = <&rst NUMAKER_SPI1_RST>; + clocks = <&pcc NUMAKER_SPI1_MODULE NUMAKER_CLK_CLKSEL2_SPI1SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi2: spi@40063000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40063000 0x6c>; + interrupts = <52 0>; + resets = <&rst NUMAKER_SPI2_RST>; + clocks = <&pcc NUMAKER_SPI2_MODULE NUMAKER_CLK_CLKSEL3_SPI2SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi3: spi@40064000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40064000 0x6c>; + interrupts = <62 0>; + resets = <&rst NUMAKER_SPI3_RST>; + clocks = <&pcc NUMAKER_SPI3_MODULE NUMAKER_CLK_CLKSEL3_SPI3SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi4: spi@40065000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40065000 0x6c>; + interrupts = <63 0>; + resets = <&rst NUMAKER_SPI4_RST>; + clocks = <&pcc NUMAKER_SPI4_MODULE NUMAKER_CLK_CLKSEL4_SPI4SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi5: spi@40066000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40066000 0x6c>; + interrupts = <57 0>; + resets = <&rst NUMAKER_SPI5_RST>; + clocks = <&pcc NUMAKER_SPI5_MODULE NUMAKER_CLK_CLKSEL4_SPI5SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi6: spi@40067000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40067000 0x6c>; + interrupts = <70 0>; + resets = <&rst NUMAKER_SPI6_RST>; + clocks = <&pcc NUMAKER_SPI6_MODULE NUMAKER_CLK_CLKSEL4_SPI6SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi7: spi@40068000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x40068000 0x6c>; + interrupts = <77 0>; + resets = <&rst NUMAKER_SPI7_RST>; + clocks = <&pcc NUMAKER_SPI7_MODULE NUMAKER_CLK_CLKSEL4_SPI7SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi8: spi@4006b000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x4006b000 0x6c>; + interrupts = <108 0>; + resets = <&rst NUMAKER_SPI8_RST>; + clocks = <&pcc NUMAKER_SPI8_MODULE NUMAKER_CLK_CLKSEL4_SPI8SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi9: spi@4006c000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x4006c000 0x6c>; + interrupts = <111 0>; + resets = <&rst NUMAKER_SPI9_RST>; + clocks = <&pcc NUMAKER_SPI9_MODULE NUMAKER_CLK_CLKSEL4_SPI9SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + spi10: spi@4006d000 { + compatible = "nuvoton,numaker-spi"; + reg = <0x4006d000 0x6c>; + interrupts = <119 0>; + resets = <&rst NUMAKER_SPI10_RST>; + clocks = <&pcc NUMAKER_SPI10_MODULE NUMAKER_CLK_CLKSEL4_SPI10SEL_HIRC 0>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; }; }; diff --git a/dts/bindings/spi/nuvoton,numaker-spi.yaml b/dts/bindings/spi/nuvoton,numaker-spi.yaml new file mode 100644 index 00000000000..e52ebc33f6d --- /dev/null +++ b/dts/bindings/spi/nuvoton,numaker-spi.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton, NuMaker SPI controller + +compatible: "nuvoton,numaker-spi" + +include: [spi-controller.yaml, reset-device.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + resets: + required: true + + clocks: + required: true