Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2018 SiFive Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT sifive_spi0

#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(spi_sifive);

#include "spi_sifive.h"

#include <stdbool.h>

/* Helper Functions */

static inline void sys_set_mask(mem_addr_t addr, uint32_t mask, uint32_t value)
{
	uint32_t temp = sys_read32(addr);

	temp &= ~(mask);
	temp |= value;

	sys_write32(temp, addr);
}

int spi_config(const struct device *dev, uint32_t frequency,
	       uint16_t operation)
{
	uint32_t div;
	uint32_t fmt_len;

	if (SPI_OP_MODE_GET(operation) != SPI_OP_MODE_MASTER) {
		return -ENOTSUP;
	}

	if (operation & SPI_MODE_LOOP) {
		return -ENOTSUP;
	}

	/* Set the SPI frequency */
	div = (SPI_CFG(dev)->f_sys / (frequency * 2U)) - 1;
	sys_write32((SF_SCKDIV_DIV_MASK & div), SPI_REG(dev, REG_SCKDIV));

	/* Set the polarity */
	if (operation & SPI_MODE_CPOL) {
		/* If CPOL is set, then SCK idles at logical 1 */
		sys_set_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_POL);
	} else {
		/* SCK idles at logical 0 */
		sys_clear_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_POL);
	}

	/* Set the phase */
	if (operation & SPI_MODE_CPHA) {
		/*
		 * If CPHA is set, then data is sampled
		 * on the trailing SCK edge
		 */
		sys_set_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_PHA);
	} else {
		/* Data is sampled on the leading SCK edge */
		sys_clear_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_PHA);
	}

	/* Get the frame length */
	fmt_len = SPI_WORD_SIZE_GET(operation);
	if (fmt_len > SF_FMT_LEN_MASK) {
		return -ENOTSUP;
	}

	/* Set the frame length */
	fmt_len = fmt_len << SF_FMT_LEN;
	fmt_len &= SF_FMT_LEN_MASK;
	sys_set_mask(SPI_REG(dev, REG_FMT), SF_FMT_LEN_MASK, fmt_len);

	if ((operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) {
		return -ENOTSUP;
	}
	/* Set single line operation */
	sys_set_mask(SPI_REG(dev, REG_FMT),
		SF_FMT_PROTO_MASK,
		SF_FMT_PROTO_SINGLE);

	/* Set the endianness */
	if (operation & SPI_TRANSFER_LSB) {
		sys_set_bit(SPI_REG(dev, REG_FMT), SF_FMT_ENDIAN);
	} else {
		sys_clear_bit(SPI_REG(dev, REG_FMT), SF_FMT_ENDIAN);
	}

	return 0;
}

void spi_sifive_send(const struct device *dev, uint16_t frame)
{
	while (sys_read32(SPI_REG(dev, REG_TXDATA)) & SF_TXDATA_FULL) {
	}

	sys_write32((uint32_t) frame, SPI_REG(dev, REG_TXDATA));
}

uint16_t spi_sifive_recv(const struct device *dev)
{
	uint32_t val;

	while ((val = sys_read32(SPI_REG(dev, REG_RXDATA))) & SF_RXDATA_EMPTY) {
	}

	return (uint16_t) val;
}

void spi_sifive_xfer(const struct device *dev, const bool hw_cs_control)
{
	struct spi_context *ctx = &SPI_DATA(dev)->ctx;
	uint16_t txd, rxd;

	do {
		/* Send a frame */
		if (spi_context_tx_buf_on(ctx)) {
			txd = *ctx->tx_buf;
		} else {
			txd = 0U;
		}

		spi_sifive_send(dev, txd);

		spi_context_update_tx(ctx, 1, 1);

		/* Receive a frame */
		rxd = spi_sifive_recv(dev);

		if (spi_context_rx_buf_on(ctx)) {
			*ctx->rx_buf = rxd;
		}

		spi_context_update_rx(ctx, 1, 1);
	} while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx));

	/* Deassert the CS line */
	if (!hw_cs_control) {
		spi_context_cs_control(&SPI_DATA(dev)->ctx, false);
	} else {
		sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE));
	}

	spi_context_complete(ctx, 0);
}

/* API Functions */

int spi_sifive_init(const struct device *dev)
{
	/* Disable SPI Flash mode */
	sys_clear_bit(SPI_REG(dev, REG_FCTRL), SF_FCTRL_EN);

	/* Make sure the context is unlocked */
	spi_context_unlock_unconditionally(&SPI_DATA(dev)->ctx);
	return 0;
}

int spi_sifive_transceive(const struct device *dev,
			  const struct spi_config *config,
			  const struct spi_buf_set *tx_bufs,
			  const struct spi_buf_set *rx_bufs)
{
	int rc = 0;
	bool hw_cs_control = false;

	/* Lock the SPI Context */
	spi_context_lock(&SPI_DATA(dev)->ctx, false, NULL, config);

	/* Configure the SPI bus */
	SPI_DATA(dev)->ctx.config = config;

	/*
	 * If the chip select configuration is not present, we'll ask the
	 * SPI peripheral itself to control the CS line
	 */
	if (config->cs == NULL) {
		hw_cs_control = true;
	}

	if (!hw_cs_control) {
		/*
		 * If the user has requested manual GPIO control, ask the
		 * context for control and disable HW control
		 */
		spi_context_cs_configure(&SPI_DATA(dev)->ctx);
		sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE));
	} else {
		/*
		 * Tell the hardware to control the requested CS pin.
		 * NOTE:
		 *	For the SPI peripheral, the pin number is not the
		 *	GPIO pin, but the index into the list of available
		 *	CS lines for the SPI peripheral.
		 */
		sys_write32(config->slave, SPI_REG(dev, REG_CSID));
		sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE));
	}

	rc = spi_config(dev, config->frequency, config->operation);
	if (rc < 0) {
		spi_context_release(&SPI_DATA(dev)->ctx, rc);
		return rc;
	}

	spi_context_buffers_setup(&SPI_DATA(dev)->ctx, tx_bufs, rx_bufs, 1);

	/* Assert the CS line */
	if (!hw_cs_control) {
		spi_context_cs_control(&SPI_DATA(dev)->ctx, true);
	} else {
		sys_write32(SF_CSMODE_HOLD, SPI_REG(dev, REG_CSMODE));
	}

	/* Perform transfer */
	spi_sifive_xfer(dev, hw_cs_control);

	rc = spi_context_wait_for_completion(&SPI_DATA(dev)->ctx);

	spi_context_release(&SPI_DATA(dev)->ctx, rc);

	return rc;
}

int spi_sifive_release(const struct device *dev,
		       const struct spi_config *config)
{
	spi_context_unlock_unconditionally(&SPI_DATA(dev)->ctx);
	return 0;
}

/* Device Instantiation */

static struct spi_driver_api spi_sifive_api = {
	.transceive = spi_sifive_transceive,
	.release = spi_sifive_release,
};

#define SPI_INIT(n)	\
	static struct spi_sifive_data spi_sifive_data_##n = { \
		SPI_CONTEXT_INIT_LOCK(spi_sifive_data_##n, ctx), \
		SPI_CONTEXT_INIT_SYNC(spi_sifive_data_##n, ctx), \
	}; \
	static struct spi_sifive_cfg spi_sifive_cfg_##n = { \
		.base = DT_INST_REG_ADDR_BY_NAME(n, control), \
		.f_sys = DT_INST_PROP(n, clock_frequency), \
	}; \
	DEVICE_DT_INST_DEFINE(n, \
			spi_sifive_init, \
			NULL, \
			&spi_sifive_data_##n, \
			&spi_sifive_cfg_##n, \
			POST_KERNEL, \
			CONFIG_SPI_INIT_PRIORITY, \
			&spi_sifive_api)

#ifndef CONFIG_SIFIVE_SPI_0_ROM
#if DT_INST_NODE_HAS_PROP(0, label)

SPI_INIT(0);

#endif /* DT_INST_NODE_HAS_PROP(0, label) */
#endif /* !CONFIG_SIFIVE_SPI_0_ROM */

#if DT_INST_NODE_HAS_PROP(1, label)

SPI_INIT(1);

#endif /* DT_INST_NODE_HAS_PROP(1, label) */

#if DT_INST_NODE_HAS_PROP(2, label)

SPI_INIT(2);

#endif /* DT_INST_NODE_HAS_PROP(2, label) */