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) 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 <errno.h>

#include <device.h>
#include <spi.h>
#include <gpio.h>
#include <board.h>

#include "qm_ss_spi.h"
#include "qm_ss_isr.h"
#include "ss_clk.h"

struct ss_pending_transfer {
	struct device *dev;
	qm_ss_spi_async_transfer_t xfer;
};

static struct ss_pending_transfer pending_transfers[2];

struct ss_spi_qmsi_config {
	qm_ss_spi_t spi;
#ifdef CONFIG_SPI_CS_GPIO
	char *cs_port;
	uint32_t cs_pin;
#endif
};

struct ss_spi_qmsi_runtime {
#ifdef CONFIG_SPI_CS_GPIO
	struct device *gpio_cs;
#endif
	device_sync_call_t sync;
	struct nano_sem sem;
	qm_ss_spi_config_t cfg;
	int rc;
	bool loopback;
};

static inline qm_ss_spi_bmode_t config_to_bmode(uint8_t mode)
{
	switch (mode) {
	case SPI_MODE_CPHA:
		return QM_SS_SPI_BMODE_1;
	case SPI_MODE_CPOL:
		return QM_SS_SPI_BMODE_2;
	case SPI_MODE_CPOL | SPI_MODE_CPHA:
		return QM_SS_SPI_BMODE_3;
	default:
		return QM_SS_SPI_BMODE_0;
	}
}

#ifdef CONFIG_SPI_CS_GPIO
static void spi_control_cs(struct device *dev, bool active)
{
	struct ss_spi_qmsi_runtime *context = dev->driver_data;
	struct ss_spi_qmsi_config *config = dev->config->config_info;
	struct device *gpio = context->gpio_cs;

	if (!gpio)
		return;

	gpio_pin_write(gpio, config->cs_pin, !active);
}
#endif

static int ss_spi_qmsi_configure(struct device *dev,
				 struct spi_config *config)
{
	struct ss_spi_qmsi_runtime *context = dev->driver_data;
	qm_ss_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 0;
}

static void spi_qmsi_callback(void *data, int error, qm_ss_spi_status_t status,
			      uint16_t len)
{
	struct ss_spi_qmsi_config *spi_config =
			       ((struct device *)data)->config->config_info;
	qm_ss_spi_t spi_id = spi_config->spi;
	struct ss_pending_transfer *pending = &pending_transfers[spi_id];
	struct device *dev = pending->dev;
	struct ss_spi_qmsi_runtime *context;

	if (!dev)
		return;

	context = dev->driver_data;

#ifdef CONFIG_SPI_CS_GPIO
	spi_control_cs(dev, false);
#endif

	pending->dev = NULL;
	context->rc = error;
	device_sync_call_complete(&context->sync);
}

static int ss_spi_qmsi_slave_select(struct device *dev, uint32_t slave)
{
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;
	qm_ss_spi_t spi_id = spi_config->spi;

	return qm_ss_spi_slave_select(spi_id, 1 << (slave - 1)) ? -EIO : 0;
}

static inline uint8_t frame_size_to_dfs(qm_ss_spi_frame_size_t frame_size)
{
	if (frame_size <= QM_SS_SPI_FRAME_SIZE_8_BIT) {
		return 1;
	}

	if (frame_size <= QM_SS_SPI_FRAME_SIZE_16_BIT) {
		return 2;
	}

	/* This should never happen, it will crash later on. */
	return 0;
}

static int ss_spi_qmsi_transceive(struct device *dev,
				  const void *tx_buf, uint32_t tx_buf_len,
				  void *rx_buf, uint32_t rx_buf_len)
{
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;
	qm_ss_spi_t spi_id = spi_config->spi;
	struct ss_spi_qmsi_runtime *context = dev->driver_data;
	qm_ss_spi_config_t *cfg = &context->cfg;
	uint8_t dfs = frame_size_to_dfs(cfg->frame_size);
	qm_ss_spi_async_transfer_t *xfer;
	int rc;

	nano_sem_take(&context->sem, TICKS_UNLIMITED);
	if (pending_transfers[spi_id].dev) {
		nano_sem_give(&context->sem);
		return -EBUSY;
	}
	pending_transfers[spi_id].dev = dev;
	nano_sem_give(&context->sem);

	xfer = &pending_transfers[spi_id].xfer;

	xfer->rx = rx_buf;
	xfer->rx_len = rx_buf_len / dfs;
	xfer->tx = (uint8_t *)tx_buf;
	xfer->tx_len = tx_buf_len / dfs;
	xfer->callback_data = dev;
	xfer->callback = spi_qmsi_callback;

	if (tx_buf_len == 0) {
		cfg->transfer_mode = QM_SS_SPI_TMOD_RX;
	} else if (rx_buf_len == 0) {
		cfg->transfer_mode = QM_SS_SPI_TMOD_TX;
	} else {
		cfg->transfer_mode = QM_SS_SPI_TMOD_TX_RX;
	}

	if (context->loopback) {
		uint32_t ctrl;

		if (spi_id == 0) {
			ctrl = __builtin_arc_lr(QM_SS_SPI_0_BASE +
						QM_SS_SPI_CTRL);
			ctrl |= BIT(11);
			__builtin_arc_sr(ctrl, QM_SS_SPI_0_BASE +
					 QM_SS_SPI_CTRL);
		} else {
			ctrl = __builtin_arc_lr(QM_SS_SPI_1_BASE +
						QM_SS_SPI_CTRL);
			ctrl |= BIT(11);
			__builtin_arc_sr(ctrl, QM_SS_SPI_1_BASE +
					 QM_SS_SPI_CTRL);
		}
	}

	rc = qm_ss_spi_set_config(spi_id, cfg);
	if (rc != 0) {
		return -EINVAL;
	}

#ifdef CONFIG_SPI_CS_GPIO
	spi_control_cs(dev, true);
#endif

	rc = qm_ss_spi_irq_transfer(spi_id, xfer);
	if (rc != 0) {
#ifdef CONFIG_SPI_CS_GPIO
		spi_control_cs(dev, false);
#endif
		return -EIO;
	}

	device_sync_call_wait(&context->sync);

	return context->rc ? -EIO : 0;
}

static struct spi_driver_api ss_spi_qmsi_api = {
	.configure = ss_spi_qmsi_configure,
	.slave_select = ss_spi_qmsi_slave_select,
	.transceive = ss_spi_qmsi_transceive,
};

#ifdef CONFIG_SPI_CS_GPIO
static struct device *gpio_cs_init(struct ss_spi_qmsi_config *config)
{
	struct device *gpio;

	if (!config->cs_port)
		return NULL;

	gpio = device_get_binding(config->cs_port);
	if (!gpio)
		return NULL;

	gpio_pin_configure(gpio, config->cs_pin, GPIO_DIR_OUT);
	gpio_pin_write(gpio, config->cs_pin, 1);

	return gpio;
}
#endif

static int ss_spi_qmsi_init(struct device *dev);

#ifdef CONFIG_SPI_0
static struct ss_spi_qmsi_config spi_qmsi_mst_0_config = {
	.spi = QM_SS_SPI_0,
#ifdef CONFIG_SPI_CS_GPIO
	.cs_port = CONFIG_SPI_0_CS_GPIO_PORT,
	.cs_pin = CONFIG_SPI_0_CS_GPIO_PIN,
#endif
};

static struct ss_spi_qmsi_runtime spi_qmsi_mst_0_runtime;

DEVICE_INIT(ss_spi_master_0, CONFIG_SPI_0_NAME,
	    ss_spi_qmsi_init, &spi_qmsi_mst_0_runtime, &spi_qmsi_mst_0_config,
	    SECONDARY, CONFIG_SPI_INIT_PRIORITY);


#endif /* CONFIG_SPI_0 */
#ifdef CONFIG_SPI_1

static struct ss_spi_qmsi_config spi_qmsi_mst_1_config = {
	.spi = QM_SS_SPI_1,
#ifdef CONFIG_SPI_CS_GPIO
	.cs_port = CONFIG_SPI_1_CS_GPIO_PORT,
	.cs_pin = CONFIG_SPI_1_CS_GPIO_PIN,
#endif
};

static struct ss_spi_qmsi_runtime spi_qmsi_mst_1_runtime;

DEVICE_INIT(ss_spi_master_1, CONFIG_SPI_1_NAME,
	    ss_spi_qmsi_init, &spi_qmsi_mst_1_runtime, &spi_qmsi_mst_1_config,
	    SECONDARY, CONFIG_SPI_INIT_PRIORITY);

#endif /* CONFIG_SPI_1 */

static void ss_spi_err_isr(void *arg)
{
	struct device *dev = arg;
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;

	if (spi_config->spi == QM_SS_SPI_0) {
		qm_ss_spi_0_err_isr(NULL);
	} else {
		qm_ss_spi_1_err_isr(NULL);
	}
}

static void ss_spi_rx_isr(void *arg)
{
	struct device *dev = arg;
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;

	if (spi_config->spi == QM_SS_SPI_0) {
		qm_ss_spi_0_rx_isr(NULL);
	} else {
		qm_ss_spi_1_rx_isr(NULL);
	}
}

static void ss_spi_tx_isr(void *arg)
{
	struct device *dev = arg;
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;

	if (spi_config->spi == QM_SS_SPI_0) {
		qm_ss_spi_0_tx_isr(NULL);
	} else {
		qm_ss_spi_1_tx_isr(NULL);
	}
}

static int ss_spi_qmsi_init(struct device *dev)
{
	struct ss_spi_qmsi_config *spi_config = dev->config->config_info;
	struct ss_spi_qmsi_runtime *context = dev->driver_data;
	uint32_t *scss_intmask = NULL;

	switch (spi_config->spi) {
#ifdef CONFIG_SPI_0
	case QM_SS_SPI_0:
		IRQ_CONNECT(IRQ_SPI0_ERR_INT, CONFIG_SPI_0_IRQ_PRI,
			    ss_spi_err_isr, DEVICE_GET(ss_spi_master_0), 0);
		irq_enable(IRQ_SPI0_ERR_INT);

		IRQ_CONNECT(IRQ_SPI0_RX_AVAIL, CONFIG_SPI_0_IRQ_PRI,
			    ss_spi_rx_isr, DEVICE_GET(ss_spi_master_0), 0);
		irq_enable(IRQ_SPI0_RX_AVAIL);

		IRQ_CONNECT(IRQ_SPI0_TX_REQ, CONFIG_SPI_0_IRQ_PRI,
			    ss_spi_tx_isr, DEVICE_GET(ss_spi_master_0), 0);
		irq_enable(IRQ_SPI0_TX_REQ);

		ss_clk_spi_enable(0);

		/* Route SPI interrupts to Sensor Subsystem */
		scss_intmask = (uint32_t *)&QM_SCSS_INT->int_ss_spi_0;
		*scss_intmask &= ~BIT(8);
		scss_intmask++;
		*scss_intmask &= ~BIT(8);
		scss_intmask++;
		*scss_intmask &= ~BIT(8);
		break;
#endif /* CONFIG_SPI_0 */

#ifdef CONFIG_SPI_1
	case QM_SS_SPI_1:
		IRQ_CONNECT(IRQ_SPI1_ERR_INT, CONFIG_SPI_1_IRQ_PRI,
			    ss_spi_err_isr, DEVICE_GET(ss_spi_master_1), 0);
		irq_enable(IRQ_SPI1_ERR_INT);

		IRQ_CONNECT(IRQ_SPI1_RX_AVAIL, CONFIG_SPI_1_IRQ_PRI,
			    ss_spi_rx_isr, DEVICE_GET(ss_spi_master_1), 0);
		irq_enable(IRQ_SPI1_RX_AVAIL);

		IRQ_CONNECT(IRQ_SPI1_TX_REQ, CONFIG_SPI_1_IRQ_PRI,
			    ss_spi_tx_isr, DEVICE_GET(ss_spi_master_1), 0);
		irq_enable(IRQ_SPI1_TX_REQ);

		ss_clk_spi_enable(1);

		/* Route SPI interrupts to Sensor Subsystem */
		scss_intmask = (uint32_t *)&QM_SCSS_INT->int_ss_spi_1;
		*scss_intmask &= ~BIT(8);
		scss_intmask++;
		*scss_intmask &= ~BIT(8);
		scss_intmask++;
		*scss_intmask &= ~BIT(8);
		break;
#endif /* CONFIG_SPI_1 */

	default:
		return -EIO;
	}

#ifdef CONFIG_SPI_CS_GPIO
	context->gpio_cs = gpio_cs_init(spi_config);
#endif
	device_sync_call_init(&context->sync);
	nano_sem_init(&context->sem);
	nano_sem_give(&context->sem);

	dev->driver_api = &ss_spi_qmsi_api;

	return 0;
}