Commit caeda699 authored by Ren Chen's avatar Ren Chen Committed by Benjamin Cabé
Browse files

drivers: spi: add it51xxx spi driver



This commit adds it51xxx spi driver.

Tested with: samples/drivers/spi_flash

Signed-off-by: default avatarRen Chen <Ren.Chen@ite.com.tw>
parent 41a83bd3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_EMUL spi_emul.c)
zephyr_library_sources_ifdef(CONFIG_SPI_GD32 spi_gd32.c)
zephyr_library_sources_ifdef(CONFIG_SPI_GRLIB_SPIMCTRL spi_grlib_spimctrl.c)
zephyr_library_sources_ifdef(CONFIG_SPI_INFINEON_CAT1 spi_ifx_cat1.c)
zephyr_library_sources_ifdef(CONFIG_SPI_ITE_IT51XXX spi_it51xxx.c)
zephyr_library_sources_ifdef(CONFIG_SPI_ITE_IT8XXX2 spi_it8xxx2.c)
zephyr_library_sources_ifdef(CONFIG_SPI_LITEX spi_litex.c)
zephyr_library_sources_ifdef(CONFIG_SPI_LITEX_LITESPI spi_litex_litespi.c)
+1 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ source "drivers/spi/Kconfig.esp32"
source "drivers/spi/Kconfig.gd32"
source "drivers/spi/Kconfig.grlib_spimctrl"
source "drivers/spi/Kconfig.ifx_cat1"
source "drivers/spi/Kconfig.it51xxx"
source "drivers/spi/Kconfig.it8xxx2"
source "drivers/spi/Kconfig.litex"
source "drivers/spi/Kconfig.max32"
+10 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 ITE Corporation.
# SPDX-License-Identifier: Apache-2.0

config SPI_ITE_IT51XXX
	bool "ITE IT51XXX SPI Driver"
	default y
	depends on DT_HAS_ITE_IT51XXX_SPI_ENABLED
	select PINCTRL
	help
	  Enable support for the ITE IT51XXX SPI host (SSPI) driver.
+428 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 ITE Technology Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ite_it51xxx_spi

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_it51xxx, CONFIG_SPI_LOG_LEVEL);

#include <zephyr/irq.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/dt-bindings/interrupt-controller/ite-it51xxx-intc.h>
#include <zephyr/pm/policy.h>
#include <soc.h>

#include "spi_context.h"

#define SPI_CHIP_SELECT_COUNT 2

/* IT51xxx SSPI Registers Definition */
#define SPI00_DATA     0x00
#define SPI01_CTRL1    0x01
#define CLOCK_POLARTY  BIT(6)
#define CLOCK_PHASE    BIT(5)
#define SSCK_FREQ_MASK GENMASK(4, 2)
#define INTERRUPT_EN   BIT(1)

#define SPI02_CTRL2        0x02
#define READ_CYCLE         BIT(2)
#define BLOCKING_SELECTION BIT(1)

#define SPI03_STATUS         0x03
#define DEVICE_BUSY          BIT(6)
#define SPI_TRANSMISSION_END BIT(5)
#define CH0_START            BIT(4)
#define CH1_START            BIT(3)
#define TRANSFER_IN_PROGRESS BIT(2)
#define TRANSFER_END         BIT(1)
#define SPI_BUS_BUSY         BIT(0)

#define SPI04_CTRL3       0x04
#define BYTE_DONE_INT_STS BIT(4)

#define SPI05_CHAIN_CTRL           0x05
#define PLL_CLOCK_SOURCE_SELECTION BIT(6)

struct spi_it51xxx_config {
	mm_reg_t base;
	const struct pinctrl_dev_config *pcfg;

	const struct device *clk_dev;
	struct ite_clk_cfg clk_cfg;

	void (*irq_config_func)(void);

	int irq_flags;
	uint8_t irq_no;
};

struct spi_it51xxx_data {
	struct spi_context ctx;
};

static inline int spi_it51xxx_set_freq(const struct device *dev, const uint32_t frequency)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	const uint8_t freq_pll_div[8] = {2, 4, 6, 8, 10, 12, 14, 1};
	const uint8_t freq_ec_div[8] = {2, 4, 6, 8, 10, 12, 14, 16};
	uint32_t clk_pll;
	uint8_t reg_val;
	uint8_t divisor;
	int ret;

	ret = clock_control_get_rate(cfg->clk_dev, (clock_control_subsys_t)&cfg->clk_cfg, &clk_pll);
	if (ret) {
		LOG_WRN("failed to get pll frequency %d", ret);
		return ret;
	}

	for (uint8_t i = 0; i < ARRAY_SIZE(freq_pll_div); i++) {
		if (frequency == (clk_pll / freq_pll_div[i])) {
			/* select pll frequency as clock source */
			sys_write8(sys_read8(cfg->base + SPI05_CHAIN_CTRL) |
					   PLL_CLOCK_SOURCE_SELECTION,
				   cfg->base + SPI05_CHAIN_CTRL);
			divisor = i;
			LOG_DBG("freq: pll %dHz, ssck %dHz", clk_pll, frequency);
			goto out;
		}
	}

	for (uint8_t i = 0; i < ARRAY_SIZE(freq_ec_div); i++) {
		if (frequency == (IT51XXX_EC_FREQ / freq_ec_div[i])) {
			/* select ec frequency as clock source */
			sys_write8(sys_read8(cfg->base + SPI05_CHAIN_CTRL) &
					   ~PLL_CLOCK_SOURCE_SELECTION,
				   cfg->base + SPI05_CHAIN_CTRL);
			divisor = i;
			LOG_DBG("freq: ec %dHz, ssck %dHz", IT51XXX_EC_FREQ, frequency);
			goto out;
		}
	}

	LOG_ERR("unknown frequency %dHz, pll %dHz, ec %dHz", frequency, clk_pll, IT51XXX_EC_FREQ);
	return -ENOTSUP;

out:
	reg_val = sys_read8(cfg->base + SPI01_CTRL1) & ~SSCK_FREQ_MASK;
	reg_val |= FIELD_PREP(SSCK_FREQ_MASK, divisor);
	sys_write8(reg_val, cfg->base + SPI01_CTRL1);

	return 0;
}

static int spi_it51xxx_configure(const struct device *dev, const struct spi_config *spi_cfg)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	int ret;
	uint8_t reg_val;

	if (spi_cfg->slave > (SPI_CHIP_SELECT_COUNT - 1)) {
		LOG_ERR("slave %d is greater than %d", spi_cfg->slave, SPI_CHIP_SELECT_COUNT - 1);
		return -EINVAL;
	}

	LOG_DBG("chip select: %d, operation: 0x%x", spi_cfg->slave, spi_cfg->operation);

	if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_SLAVE) {
		LOG_ERR("unsupported spi slave mode");
		return -ENOTSUP;
	}

	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) {
		LOG_ERR("unsupported loopback mode");
		return -ENOTSUP;
	}

	reg_val = sys_read8(cfg->base + SPI01_CTRL1);
	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) {
		reg_val |= CLOCK_PHASE;
	} else {
		reg_val &= ~CLOCK_PHASE;
	}

	if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) {
		reg_val |= CLOCK_POLARTY;
	} else {
		reg_val &= ~CLOCK_POLARTY;
	}
	sys_write8(reg_val, cfg->base + SPI01_CTRL1);

	if (SPI_WORD_SIZE_GET(spi_cfg->operation) != 8) {
		return -ENOTSUP;
	}

	if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) &&
	    (spi_cfg->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) {
		LOG_ERR("only single line mode is supported");
		return -EINVAL;
	}

	ret = spi_it51xxx_set_freq(dev, spi_cfg->frequency);
	if (ret) {
		return ret;
	}

	/* select non-blocking mode */
	sys_write8(sys_read8(cfg->base + SPI02_CTRL2) & ~BLOCKING_SELECTION,
		   cfg->base + SPI02_CTRL2);

	ctx->config = spi_cfg;

	return 0;
}

static inline void spi_it51xxx_tx(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;

	sys_write8(sys_read8(cfg->base + SPI02_CTRL2) & ~READ_CYCLE, cfg->base + SPI02_CTRL2);

	sys_write8(ctx->tx_buf[0], cfg->base + SPI00_DATA);
	sys_write8(ctx->config->slave ? CH1_START : CH0_START, cfg->base + SPI03_STATUS);
}

static inline void spi_it51xxx_rx(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;

	sys_write8(sys_read8(cfg->base + SPI02_CTRL2) | READ_CYCLE, cfg->base + SPI02_CTRL2);
	sys_write8(ctx->config->slave ? CH1_START : CH0_START, cfg->base + SPI03_STATUS);
}

static inline bool spi_it51xxx_xfer_done(struct spi_context *ctx)
{
	return !spi_context_tx_buf_on(ctx) && !spi_context_rx_buf_on(ctx);
}

static void spi_it51xxx_next_xfer(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;

	if (spi_it51xxx_xfer_done(ctx)) {
		sys_write8(SPI_TRANSMISSION_END, cfg->base + SPI03_STATUS);
		return;
	}

	if (!spi_context_tx_on(ctx)) {
		spi_it51xxx_rx(dev);
	} else {
		spi_it51xxx_tx(dev);
	}
}

static int transceive(const struct device *dev, const struct spi_config *config,
		      const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs,
		      bool asynchronous, spi_callback_t cb, void *userdata)
{
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	int ret;

	spi_context_lock(ctx, asynchronous, cb, userdata, config);

	/* configure spi */
	ret = spi_it51xxx_configure(dev, config);
	if (ret) {
		goto out;
	}

	spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1);
	pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);

	LOG_DBG("tx/rx: %d/%d", spi_context_total_tx_len(ctx), spi_context_total_rx_len(ctx));

	spi_it51xxx_next_xfer(dev);
	ret = spi_context_wait_for_completion(ctx);

	pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);

out:
	spi_context_release(ctx, ret);

	return ret;
}

static int it51xxx_transceive(const struct device *dev, const struct spi_config *config,
			      const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs)
{
	return transceive(dev, config, tx_bufs, rx_bufs, false, NULL, NULL);
}

#ifdef CONFIG_SPI_ASYNC
static int it51xxx_transceive_async(const struct device *dev, const struct spi_config *config,
				    const struct spi_buf_set *tx_bufs,
				    const struct spi_buf_set *rx_bufs, spi_callback_t cb,
				    void *userdata)
{
	return transceive(dev, config, tx_bufs, rx_bufs, true, cb, userdata);
}
#endif /* CONFIG_SPI_ASYNC */

static int it51xxx_release(const struct device *dev, const struct spi_config *config)
{
	ARG_UNUSED(config);

	struct spi_it51xxx_data *data = dev->data;

	spi_context_unlock_unconditionally(&data->ctx);

	return 0;
}

static inline bool is_read_cycle(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;

	return sys_read8(cfg->base + SPI02_CTRL2) & READ_CYCLE;
}

static void it51xxx_spi_byte_done_handle(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;

	if (is_read_cycle(dev)) {
		*ctx->rx_buf = sys_read8(cfg->base + SPI00_DATA);
		spi_context_update_rx(ctx, 1, 1);
	} else {
		spi_context_update_tx(ctx, 1, 1);
	}

	/* it51xxx spi driver accommodates two scenarios: when spi rx buffer
	 * either exclude tx data pointer and length, or include them but with
	 * null pointer.
	 */
	if (!ctx->rx_buf) {
		spi_context_update_rx(ctx, 1, ctx->rx_len);
	}

	spi_it51xxx_next_xfer(dev);
}

static void it51xxx_spi_isr(const void *arg)
{
	const struct device *dev = arg;
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	uint8_t int_sts;

	int_sts = sys_read8(cfg->base + SPI03_STATUS);
	LOG_DBG("isr: status 0x%x", int_sts);

	if (int_sts & DEVICE_BUSY) {
		LOG_DBG("isr: device is busy");
	}

	if (int_sts & TRANSFER_IN_PROGRESS) {
		LOG_DBG("isr: transfer is in progress");
	}

	if (int_sts & SPI_BUS_BUSY) {
		LOG_DBG("isr: spi bus is busy");
	}

	if (int_sts & TRANSFER_END) {
		LOG_DBG("isr: transaction finished");
		sys_write8(TRANSFER_END, cfg->base + SPI03_STATUS);
		spi_context_complete(&data->ctx, dev, 0);
	}

	int_sts = sys_read8(cfg->base + SPI04_CTRL3);
	if (int_sts & BYTE_DONE_INT_STS) {
		LOG_DBG("isr: byte transfer is done");
		sys_write8(int_sts, cfg->base + SPI04_CTRL3);
		it51xxx_spi_byte_done_handle(dev);
	}
}

static int spi_it51xxx_init(const struct device *dev)
{
	const struct spi_it51xxx_config *cfg = dev->config;
	struct spi_it51xxx_data *data = dev->data;
	int ret;

	ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret) {
		LOG_ERR("failed to set default pinctrl, ret %d", ret);
		return ret;
	}

	ret = clock_control_on(cfg->clk_dev, (clock_control_subsys_t *)&cfg->clk_cfg);
	if (ret) {
		LOG_ERR("failed to turn on spi clock %d", ret);
		return ret;
	}

	if (cfg->irq_flags != IRQ_TYPE_EDGE_RISING) {
		LOG_ERR("only supports rising-edge-triggered");
		return -ENOTSUP;
	}
	ite_intc_irq_polarity_set(cfg->irq_no, cfg->irq_flags);

	/* write 1 clear interrupt status and enable interrupt */
	sys_write8(sys_read8(cfg->base + SPI03_STATUS), cfg->base + SPI03_STATUS);
	sys_write8(BYTE_DONE_INT_STS, cfg->base + SPI04_CTRL3);
	sys_write8(sys_read8(cfg->base + SPI01_CTRL1) | INTERRUPT_EN, cfg->base + SPI01_CTRL1);

	cfg->irq_config_func();

	spi_context_unlock_unconditionally(&data->ctx);

	return 0;
}

static DEVICE_API(spi, spi_it51xxx_driver_api) = {
	.transceive = it51xxx_transceive,
	.release = it51xxx_release,

#ifdef CONFIG_SPI_ASYNC
	.transceive_async = it51xxx_transceive_async,
#endif
};

#define SPI_IT51XXX_INIT(n)                                                                        \
	PINCTRL_DT_INST_DEFINE(n);                                                                 \
	static void it51xxx_spi_config_func_##n(void)                                              \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(n), 0, it51xxx_spi_isr, DEVICE_DT_INST_GET(n), 0);        \
		irq_enable(DT_INST_IRQN(n));                                                       \
	};                                                                                         \
	static const struct spi_it51xxx_config spi_it51xxx_cfg_##n = {                             \
		.base = DT_INST_REG_ADDR(n),                                                       \
		.irq_config_func = it51xxx_spi_config_func_##n,                                    \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                         \
		.clk_dev = DEVICE_DT_GET(DT_INST_PHANDLE(n, clocks)),                              \
		.clk_cfg =                                                                         \
			{                                                                          \
				.ctrl = DT_INST_CLOCKS_CELL(n, ctrl),                              \
				.bits = DT_INST_CLOCKS_CELL(n, bits),                              \
			},                                                                         \
		.irq_no = DT_INST_IRQ(n, irq),                                                     \
		.irq_flags = DT_INST_IRQ(n, flags),                                                \
	};                                                                                         \
                                                                                                   \
	static struct spi_it51xxx_data spi_it51xxx_data_##n = {                                    \
		SPI_CONTEXT_INIT_LOCK(spi_it51xxx_data_##n, ctx),                                  \
		SPI_CONTEXT_INIT_SYNC(spi_it51xxx_data_##n, ctx),                                  \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(n, &spi_it51xxx_init, NULL, &spi_it51xxx_data_##n,                   \
			      &spi_it51xxx_cfg_##n, POST_KERNEL,                                   \
			      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &spi_it51xxx_driver_api);

DT_INST_FOREACH_STATUS_OKAY(SPI_IT51XXX_INIT)
+22 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 ITE Corporation. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

description: IT51XXX SPI

compatible: "ite,it51xxx-spi"

include: [spi-controller.yaml, pinctrl-device.yaml]

properties:
  reg:
    required: true

  interrupts:
    required: true

  pinctrl-names:
    required: true

  pinctrl-0:
    type: phandles
    required: true
Loading