blob: b0ad981704a9e99ce1c3583dc5f29cca070f4e62 [file] [log] [blame]
/*
* 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)