zephyr/drivers/spi/spi_intel.c
Ramakrishna Pallala e1639b5345 device: Extend device_set_power_state API to support async requests
The existing device_set_power_state() API works only in synchronous
mode and this is not desirable for devices(ex: Gyro) which take
longer time (few 100 mSec) to suspend/resume.

To support async mode, a new callback argument is added to the API.
The device drivers can asynchronously suspend/resume and call the
callback function upon completion of the async request.

This commit adds the missing callback parameter to all the drivers
to make it compliant with the new API.

Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
2019-03-14 14:26:15 +01:00

490 lines
11 KiB
C

/* spi_intel.c - Driver implementation for Intel SPI controller */
/*
* Copyright (c) 2015 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_DOMAIN "SPI Intel"
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(spi_intel);
#include <errno.h>
#include <kernel.h>
#include <arch/cpu.h>
#include <misc/__assert.h>
#include <soc.h>
#include <init.h>
#include <sys_io.h>
#include <power.h>
#include <spi.h>
#include "spi_intel.h"
#ifdef CONFIG_IOAPIC
#include <drivers/ioapic.h>
#endif
static void completed(struct device *dev, u32_t error)
{
struct spi_intel_data *spi = dev->driver_data;
if (error) {
goto out;
}
if (spi_context_tx_on(&spi->ctx) ||
spi_context_rx_on(&spi->ctx)) {
return;
}
out:
write_sscr1(spi->sscr1, spi->regs);
clear_bit_sscr0_sse(spi->regs);
spi_context_cs_control(&spi->ctx, false);
LOG_DBG("SPI transaction completed %s error",
error ? "with" : "without");
spi_context_complete(&spi->ctx, error ? -EIO : 0);
}
static void pull_data(struct device *dev)
{
struct spi_intel_data *spi = dev->driver_data;
while (read_sssr(spi->regs) & INTEL_SPI_SSSR_RNE) {
u32_t data = read_ssdr(spi->regs);
if (spi_context_rx_buf_on(&spi->ctx)) {
switch (spi->dfs) {
case 1:
UNALIGNED_PUT(data, (u8_t *)spi->ctx.rx_buf);
break;
case 2:
UNALIGNED_PUT(data, (u16_t *)spi->ctx.rx_buf);
break;
case 4:
UNALIGNED_PUT(data, (u32_t *)spi->ctx.rx_buf);
break;
}
spi_context_update_rx(&spi->ctx, spi->dfs, 1);
}
}
}
static void push_data(struct device *dev)
{
struct spi_intel_data *spi = dev->driver_data;
u32_t status;
while ((status = read_sssr(spi->regs)) & INTEL_SPI_SSSR_TNF) {
u32_t data = 0U;
if (status & INTEL_SPI_SSSR_RFS) {
break;
}
if (spi_context_tx_buf_on(&spi->ctx)) {
switch (spi->dfs) {
case 1:
data = UNALIGNED_GET((u8_t *)
(spi->ctx.tx_buf));
break;
case 2:
data = UNALIGNED_GET((u16_t *)
(spi->ctx.tx_buf));
break;
case 4:
data = UNALIGNED_GET((u32_t *)
(spi->ctx.tx_buf));
break;
}
}
write_ssdr(data, spi->regs);
spi_context_update_tx(&spi->ctx, spi->dfs, 1);
}
if (!spi_context_tx_on(&spi->ctx)) {
clear_bit_sscr1_tie(spi->regs);
}
}
static int spi_intel_configure(struct device *dev,
const struct spi_config *config)
{
struct spi_intel_data *spi = dev->driver_data;
LOG_DBG("%p (0x%x), %p", dev, spi->regs, config);
if (spi_context_configured(&spi->ctx, config)) {
/* Nothing to do */
return 0;
}
if (config->operation & (SPI_OP_MODE_SLAVE || SPI_TRANSFER_LSB
|| SPI_LINES_DUAL || SPI_LINES_QUAD ||
SPI_LINES_OCTAL)) {
return -EINVAL;
}
/* Determine how many bytes are required per-frame */
spi->dfs = SPI_WS_TO_DFS(SPI_WORD_SIZE_GET(config->operation));
/* Pre-configuring the registers to a clean state*/
write_sscr0(0, spi->regs);
write_sscr1(0, spi->regs);
/* Word size and clock rate */
spi->sscr0 = INTEL_SPI_SSCR0_DSS(SPI_WORD_SIZE_GET(config->operation)) |
INTEL_SPI_SSCR0_SCR(config->operation);
/* Tx/Rx thresholds
* Note: Rx thresholds needs to be 1, it does not seem to be able
* to trigger reliably any interrupt with another value though the
* rx fifo would be full
*/
spi->sscr1 = INTEL_SPI_SSCR1_TFT(INTEL_SPI_SSCR1_TFT_DFLT) |
INTEL_SPI_SSCR1_RFT(INTEL_SPI_SSCR1_RFT_DFLT);
/* SPI mode */
if (SPI_MODE_GET(config->operation) & SPI_MODE_CPOL) {
spi->sscr1 |= INTEL_SPI_SSCR1_SPO;
}
if (SPI_MODE_GET(config->operation) & SPI_MODE_CPHA) {
spi->sscr1 |= INTEL_SPI_SSCR1_SPH;
}
if (SPI_MODE_GET(config->operation) & SPI_MODE_LOOP) {
spi->sscr1 |= INTEL_SPI_SSCR1_LBM;
}
/* Configuring the rate */
write_dds_rate(INTEL_SPI_DSS_RATE(config->frequency), spi->regs);
spi_context_cs_configure(&spi->ctx);
return 0;
}
static int transceive(struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
bool asynchronous,
struct k_poll_signal *signal)
{
struct spi_intel_data *spi = dev->driver_data;
int ret;
/* Check status */
if (test_bit_sscr0_sse(spi->regs) && test_bit_sssr_bsy(spi->regs)) {
LOG_DBG("Controller is busy");
return -EBUSY;
}
spi_context_lock(&spi->ctx, asynchronous, signal);
ret = spi_intel_configure(dev, config);
if (ret) {
goto out;
}
/* Set buffers info */
spi_context_buffers_setup(&spi->ctx, tx_bufs, rx_bufs, spi->dfs);
spi_context_cs_control(&spi->ctx, true);
/* Installing and Enabling the controller */
write_sscr0(spi->sscr0 | INTEL_SPI_SSCR0_SSE, spi->regs);
write_sscr1(spi->sscr1 | INTEL_SPI_SSCR1_RIE | INTEL_SPI_SSCR1_TIE,
spi->regs);
ret = spi_context_wait_for_completion(&spi->ctx);
out:
spi_context_release(&spi->ctx, ret);
return ret;
}
static int spi_intel_transceive(struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
LOG_DBG("%p, %p, %p", dev, tx_bufs, rx_bufs);
return transceive(dev, config, tx_bufs, rx_bufs, false, NULL);
}
#ifdef CONFIG_SPI_ASYNC
static int spi_intel_transceive_async(struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
struct k_poll_signal *async)
{
LOG_DBG("%p, %p, %p, %p", dev, tx_bufs, rx_bufs, async);
return transceive(dev, config, tx_bufs, rx_bufs, true, async);
}
#endif /* CONFIG_SPI_ASYNC */
static int spi_intel_release(struct device *dev,
const struct spi_config *config)
{
struct spi_intel_data *spi = dev->driver_data;
if (test_bit_sscr0_sse(spi->regs) && test_bit_sssr_bsy(spi->regs)) {
LOG_DBG("Controller is busy");
return -EBUSY;
}
spi_context_unlock_unconditionally(&spi->ctx);
return 0;
}
void spi_intel_isr(struct device *dev)
{
struct spi_intel_data *spi = dev->driver_data;
u32_t error = 0U;
u32_t status;
LOG_DBG("%p", dev);
status = read_sssr(spi->regs);
if (status & INTEL_SPI_SSSR_ROR) {
/* Unrecoverable error, ack it */
clear_bit_sssr_ror(spi->regs);
error = 1U;
goto out;
}
if (status & INTEL_SPI_SSSR_RFS) {
pull_data(dev);
}
if (test_bit_sscr1_tie(spi->regs)) {
if (status & INTEL_SPI_SSSR_TFS) {
push_data(dev);
}
}
out:
completed(dev, error);
}
static const struct spi_driver_api intel_spi_api = {
.transceive = spi_intel_transceive,
#ifdef CONFIG_SPI_ASYNC
.transceive_async = spi_intel_transceive_async,
#endif /* CONFIG_SPI_ASYNC */
.release = spi_intel_release,
};
#ifdef CONFIG_PCI
static inline int spi_intel_setup(struct device *dev)
{
struct spi_intel_data *spi = dev->driver_data;
pci_bus_scan_init();
if (!pci_bus_scan(&spi->pci_dev)) {
LOG_DBG("Could not find device");
return 0;
}
#ifdef CONFIG_PCI_ENUMERATION
spi->regs = spi->pci_dev.addr;
#endif
pci_enable_regs(&spi->pci_dev);
pci_show(&spi->pci_dev);
return 1;
}
#else
#define spi_intel_setup(_unused_) (1)
#endif /* CONFIG_PCI */
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
static void spi_intel_set_power_state(struct device *dev, u32_t power_state)
{
struct spi_intel_data *spi = dev->driver_data;
spi->device_power_state = power_state;
}
#else
#define spi_intel_set_power_state(...)
#endif
int spi_intel_init(struct device *dev)
{
const struct spi_intel_config *info = dev->config->config_info;
if (!spi_intel_setup(dev)) {
return -EPERM;
}
info->config_func();
spi_intel_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
irq_enable(info->irq);
LOG_DBG("SPI Intel Driver initialized on device: %p", dev);
return 0;
}
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
static u32_t spi_intel_get_power_state(struct device *dev)
{
struct spi_intel_data *spi = dev->driver_data;
return spi->device_power_state;
}
static int spi_intel_suspend(struct device *dev)
{
const struct spi_intel_config *info = dev->config->config_info;
struct spi_intel_data *spi = dev->driver_data;
LOG_DBG("%p", dev);
clear_bit_sscr0_sse(spi->regs);
irq_disable(info->irq);
spi_intel_set_power_state(dev, DEVICE_PM_SUSPEND_STATE);
return 0;
}
static int spi_intel_resume_from_suspend(struct device *dev)
{
const struct spi_intel_config *info = dev->config->config_info;
struct spi_intel_data *spi = dev->driver_data;
LOG_DBG("%p", dev);
set_bit_sscr0_sse(spi->regs);
irq_enable(info->irq);
spi_intel_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
return 0;
}
/*
* Implements the driver control management functionality
* the *context may include IN data or/and OUT data
*/
static int spi_intel_device_ctrl(struct device *dev, u32_t ctrl_command,
void *context, device_pm_cb cb, void *arg)
{
int ret = 0;
if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
if (*((u32_t *)context) == DEVICE_PM_SUSPEND_STATE) {
ret = spi_intel_suspend(dev);
} else if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) {
ret = spi_intel_resume_from_suspend(dev);
}
} else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) {
*((u32_t *)context) = spi_intel_get_power_state(dev);
}
if (cb) {
cb(dev, ret, context, arg);
}
return ret;
}
#else
#define spi_intel_set_power_state(...)
#endif
/* system bindings */
#ifdef CONFIG_SPI_0
void spi_config_0_irq(void);
struct spi_intel_data spi_intel_data_port_0 = {
SPI_CONTEXT_INIT_LOCK(spi_intel_data_port_0, ctx),
SPI_CONTEXT_INIT_SYNC(spi_intel_data_port_0, ctx),
.regs = DT_SPI_0_BASE_ADDRESS,
#if CONFIG_PCI
.pci_dev.class_type = SPI_INTEL_CLASS,
.pci_dev.bus = SPI_INTEL_PORT_0_BUS,
.pci_dev.dev = SPI_INTEL_PORT_0_DEV,
.pci_dev.vendor_id = SPI_INTEL_VENDOR_ID,
.pci_dev.device_id = SPI_INTEL_DEVICE_ID,
.pci_dev.function = SPI_INTEL_PORT_0_FUNCTION,
#endif
};
const struct spi_intel_config spi_intel_config_0 = {
.irq = DT_SPI_0_IRQ,
.config_func = spi_config_0_irq
};
DEVICE_DEFINE(spi_intel_port_0, DT_SPI_0_NAME, spi_intel_init,
spi_intel_device_ctrl, &spi_intel_data_port_0,
&spi_intel_config_0, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY,
&intel_spi_api);
void spi_config_0_irq(void)
{
IRQ_CONNECT(DT_SPI_0_IRQ, DT_SPI_0_IRQ_PRI,
spi_intel_isr, DEVICE_GET(spi_intel_port_0),
DT_SPI_0_IRQ_FLAGS);
}
#endif /* CONFIG_SPI_0 */
#ifdef CONFIG_SPI_1
void spi_config_1_irq(void);
struct spi_intel_data spi_intel_data_port_1 = {
SPI_CONTEXT_INIT_LOCK(spi_intel_data_port_1, ctx),
SPI_CONTEXT_INIT_SYNC(spi_intel_data_port_1, ctx),
.regs = DT_SPI_1_BASE_ADDRESS,
#if CONFIG_PCI
.pci_dev.class_type = SPI_INTEL_CLASS,
.pci_dev.bus = SPI_INTEL_PORT_1_BUS,
.pci_dev.dev = SPI_INTEL_PORT_1_DEV,
.pci_dev.function = SPI_INTEL_PORT_1_FUNCTION,
.pci_dev.vendor_id = SPI_INTEL_VENDOR_ID,
.pci_dev.device_id = SPI_INTEL_DEVICE_ID,
#endif
};
const struct spi_intel_config spi_intel_config_1 = {
.irq = DT_SPI_1_IRQ,
.config_func = spi_config_1_irq
};
DEVICE_DEFINE(spi_intel_port_1, DT_SPI_1_NAME, spi_intel_init,
spi_intel_device_ctrl, &spi_intel_data_port_1,
&spi_intel_config_1, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY,
&intel_spi_api);
void spi_config_1_irq(void)
{
IRQ_CONNECT(DT_SPI_1_IRQ, DT_SPI_1_IRQ_PRI,
spi_intel_isr, DEVICE_GET(spi_intel_port_1),
DT_SPI_1_IRQ_FLAGS);
}
#endif /* CONFIG_SPI_1 */